Public/Gui.ps1
|
<# .SYNOPSIS Launches a lightweight GUI for common O365-Toolkit tasks. .DESCRIPTION Provides a Windows Forms front end to export/import access packages, export/import Entra role assignments, and run the permission rescue helper with a couple of clicks. Intended for admins who prefer a guided flow. .NOTES Requires Windows PowerShell/PowerShell on Windows (uses System.Windows.Forms). Underlying actions still require Graph connectivity and relevant scopes. #> function Show-O365ToolkitGui { [CmdletBinding()] param() if ([System.Environment]::OSVersion.Platform -ne 'Win32NT') { Write-Warning "The GUI requires Windows (System.Windows.Forms)." return } Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $form = New-Object System.Windows.Forms.Form $form.Text = "O365 Toolkit Control Center" $form.Width = 760 $form.Height = 620 $form.StartPosition = "CenterScreen" $tabs = New-Object System.Windows.Forms.TabControl $tabs.Dock = 'Top' $tabs.Height = 480 $logBox = New-Object System.Windows.Forms.TextBox $logBox.Multiline = $true $logBox.ScrollBars = 'Vertical' $logBox.ReadOnly = $true $logBox.Dock = 'Bottom' $logBox.Height = 120 function Add-Log([string]$Message) { $timestamp = (Get-Date).ToString("HH:mm:ss") $logBox.AppendText("[$timestamp] $Message`r`n") } function Convert-JsonToHashtable([string]$InputText) { if (-not $InputText) { return @{} } try { $obj = $InputText | ConvertFrom-Json if ($obj -is [hashtable]) { return $obj } if ($obj -is [System.Collections.IDictionary]) { return @{} + $obj } Add-Log "Map text is not a JSON object; ignoring." return @{} } catch { Add-Log "Failed to parse map text as JSON; ignoring." return @{} } } # Access packages tab $tabAccess = New-Object System.Windows.Forms.TabPage $tabAccess.Text = "Access Packages" $lblExportPath = New-Object System.Windows.Forms.Label $lblExportPath.Text = "Export path" $lblExportPath.Location = '20,20' $lblExportPath.AutoSize = $true $txtExportPath = New-Object System.Windows.Forms.TextBox $txtExportPath.Location = '150,16' $txtExportPath.Width = 420 $txtExportPath.Text = ".\exports\access-packages.json" $chkIncludeResource = New-Object System.Windows.Forms.CheckBox $chkIncludeResource.Text = "Include resource role scopes" $chkIncludeResource.Location = '150,48' $chkIncludeResource.AutoSize = $true $chkIncludeResource.Checked = $true $btnExportPackages = New-Object System.Windows.Forms.Button $btnExportPackages.Text = "Export packages" $btnExportPackages.Location = '590,14' $btnExportPackages.Width = 140 $lblImportPath = New-Object System.Windows.Forms.Label $lblImportPath.Text = "Import path" $lblImportPath.Location = '20,100' $lblImportPath.AutoSize = $true $txtImportPath = New-Object System.Windows.Forms.TextBox $txtImportPath.Location = '150,96' $txtImportPath.Width = 420 $txtImportPath.Text = ".\exports\access-packages.json" $lblResourceMap = New-Object System.Windows.Forms.Label $lblResourceMap.Text = "Resource map (JSON object)" $lblResourceMap.Location = '20,140' $lblResourceMap.AutoSize = $true $txtResourceMap = New-Object System.Windows.Forms.TextBox $txtResourceMap.Location = '150,136' $txtResourceMap.Width = 420 $txtResourceMap.Height = 60 $txtResourceMap.Multiline = $true $txtResourceMap.ScrollBars = 'Vertical' $chkSkipExistingPackages = New-Object System.Windows.Forms.CheckBox $chkSkipExistingPackages.Text = "Skip existing packages by name" $chkSkipExistingPackages.Location = '150,208' $chkSkipExistingPackages.AutoSize = $true $chkSkipExistingPackages.Checked = $true $chkImportResourceRoles = New-Object System.Windows.Forms.CheckBox $chkImportResourceRoles.Text = "Import resource role scopes" $chkImportResourceRoles.Location = '150,234' $chkImportResourceRoles.AutoSize = $true $chkImportResourceRoles.Checked = $true $btnImportPackages = New-Object System.Windows.Forms.Button $btnImportPackages.Text = "Import packages" $btnImportPackages.Location = '590,94' $btnImportPackages.Width = 140 $tabAccess.Controls.AddRange(@( $lblExportPath, $txtExportPath, $chkIncludeResource, $btnExportPackages, $lblImportPath, $txtImportPath, $lblResourceMap, $txtResourceMap, $chkSkipExistingPackages, $chkImportResourceRoles, $btnImportPackages )) # Entra permissions tab $tabPerms = New-Object System.Windows.Forms.TabPage $tabPerms.Text = "Entra Permissions" $lblPermExport = New-Object System.Windows.Forms.Label $lblPermExport.Text = "Export path" $lblPermExport.Location = '20,20' $lblPermExport.AutoSize = $true $txtPermExport = New-Object System.Windows.Forms.TextBox $txtPermExport.Location = '150,16' $txtPermExport.Width = 420 $txtPermExport.Text = ".\exports\entra-permissions.json" $btnPermExport = New-Object System.Windows.Forms.Button $btnPermExport.Text = "Export assignments" $btnPermExport.Location = '590,14' $btnPermExport.Width = 140 $lblPermImport = New-Object System.Windows.Forms.Label $lblPermImport.Text = "Import path" $lblPermImport.Location = '20,70' $lblPermImport.AutoSize = $true $txtPermImport = New-Object System.Windows.Forms.TextBox $txtPermImport.Location = '150,66' $txtPermImport.Width = 420 $txtPermImport.Text = ".\exports\entra-permissions.json" $lblPrincipalMap = New-Object System.Windows.Forms.Label $lblPrincipalMap.Text = "Principal map (JSON object)" $lblPrincipalMap.Location = '20,110' $lblPrincipalMap.AutoSize = $true $txtPrincipalMap = New-Object System.Windows.Forms.TextBox $txtPrincipalMap.Location = '150,106' $txtPrincipalMap.Width = 420 $txtPrincipalMap.Height = 60 $txtPrincipalMap.Multiline = $true $txtPrincipalMap.ScrollBars = 'Vertical' $chkSkipExistingAssignments = New-Object System.Windows.Forms.CheckBox $chkSkipExistingAssignments.Text = "Skip existing assignments" $chkSkipExistingAssignments.Location = '150,178' $chkSkipExistingAssignments.AutoSize = $true $chkSkipExistingAssignments.Checked = $true $btnPermImport = New-Object System.Windows.Forms.Button $btnPermImport.Text = "Import assignments" $btnPermImport.Location = '590,64' $btnPermImport.Width = 140 $tabPerms.Controls.AddRange(@( $lblPermExport, $txtPermExport, $btnPermExport, $lblPermImport, $txtPermImport, $lblPrincipalMap, $txtPrincipalMap, $chkSkipExistingAssignments, $btnPermImport )) # Permission rescue tab $tabRescue = New-Object System.Windows.Forms.TabPage $tabRescue.Text = "Permission Rescue" $lblAction = New-Object System.Windows.Forms.Label $lblAction.Text = "Requested action" $lblAction.Location = '20,20' $lblAction.AutoSize = $true $txtAction = New-Object System.Windows.Forms.TextBox $txtAction.Location = '150,16' $txtAction.Width = 420 $txtAction.Text = "do the thing I was just denied" $lblScopes = New-Object System.Windows.Forms.Label $lblScopes.Text = "Required Graph scopes (comma)" $lblScopes.Location = '20,60' $lblScopes.AutoSize = $true $txtScopes = New-Object System.Windows.Forms.TextBox $txtScopes.Location = '150,56' $txtScopes.Width = 420 $txtScopes.Text = "EntitlementManagement.ReadWrite.All" $lblRoles = New-Object System.Windows.Forms.Label $lblRoles.Text = "Required directory roles (comma)" $lblRoles.Location = '20,100' $lblRoles.AutoSize = $true $txtRoles = New-Object System.Windows.Forms.TextBox $txtRoles.Location = '150,96' $txtRoles.Width = 420 $txtRoles.Text = "Identity Governance Administrator" $lblApprover = New-Object System.Windows.Forms.Label $lblApprover.Text = "Approver email" $lblApprover.Location = '20,140' $lblApprover.AutoSize = $true $txtApprover = New-Object System.Windows.Forms.TextBox $txtApprover.Location = '150,136' $txtApprover.Width = 420 $txtApprover.Text = "approvals@contoso.com" $lblTicket = New-Object System.Windows.Forms.Label $lblTicket.Text = "Ticket email (optional)" $lblTicket.Location = '20,180' $lblTicket.AutoSize = $true $txtTicket = New-Object System.Windows.Forms.TextBox $txtTicket.Location = '150,176' $txtTicket.Width = 420 $txtTicket.Text = "helpdesk@contoso.com" $chkSendRequest = New-Object System.Windows.Forms.CheckBox $chkSendRequest.Text = "Send request (otherwise report only)" $chkSendRequest.Location = '150,216' $chkSendRequest.AutoSize = $true $chkSendRequest.Checked = $false $btnRescue = New-Object System.Windows.Forms.Button $btnRescue.Text = "Run permission rescue" $btnRescue.Location = '590,54' $btnRescue.Width = 140 $tabRescue.Controls.AddRange(@( $lblAction, $txtAction, $lblScopes, $txtScopes, $lblRoles, $txtRoles, $lblApprover, $txtApprover, $lblTicket, $txtTicket, $chkSendRequest, $btnRescue )) $tabs.TabPages.AddRange(@($tabAccess, $tabPerms, $tabRescue)) $form.Controls.Add($tabs) $form.Controls.Add($logBox) # Wiring $btnExportPackages.Add_Click({ try { $path = $txtExportPath.Text $include = $chkIncludeResource.Checked Add-Log "Exporting access packages to $path (include resources: $include)..." Export-O365AccessPackagePlan -OutputPath $path -IncludeResourceRoleScopes:$include | Out-Null Add-Log "Export complete." } catch { Add-Log "Export failed: $_" } }) $btnImportPackages.Add_Click({ try { $path = $txtImportPath.Text $map = Convert-JsonToHashtable $txtResourceMap.Text $skip = $chkSkipExistingPackages.Checked $roles = $chkImportResourceRoles.Checked Add-Log "Importing access packages from $path (skip existing: $skip, import resources: $roles)..." Import-O365AccessPackagePlan -Path $path -ResourceMap $map -SkipExistingPackages:$skip -SkipResourceRoleScopes:(!$roles) | Out-Null Add-Log "Import complete." } catch { Add-Log "Import failed: $_" } }) $btnPermExport.Add_Click({ try { $path = $txtPermExport.Text Add-Log "Exporting Entra role assignments to $path..." Export-O365EntraPermissionsPlan -OutputPath $path | Out-Null Add-Log "Export complete." } catch { Add-Log "Export failed: $_" } }) $btnPermImport.Add_Click({ try { $path = $txtPermImport.Text $map = Convert-JsonToHashtable $txtPrincipalMap.Text $skip = $chkSkipExistingAssignments.Checked Add-Log "Importing Entra role assignments from $path (skip existing: $skip)..." Invoke-O365EntraPermissionsMigration -Path $path -PrincipalMap $map -SkipExisting:$skip | Out-Null Add-Log "Import complete." } catch { Add-Log "Import failed: $_" } }) $btnRescue.Add_Click({ try { $action = $txtAction.Text $scopes = $txtScopes.Text -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ } $roles = $txtRoles.Text -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ } $approver = $txtApprover.Text $ticket = $txtTicket.Text $send = $chkSendRequest.Checked Add-Log "Running permission rescue for '$action' (send request: $send)..." $report = Invoke-O365PermissionRescue -RequestedAction $action -RequiredGraphScopes $scopes -RequiredRoleNames $roles -ApproverEmail $approver -TicketEmail $ticket -SendRequest:$send if ($report) { Add-Log "Current scopes: $($report.CurrentScopes -join ', ')" Add-Log "Missing scopes: $($report.MissingGraphScopes -join ', ')" Add-Log "Current roles: $($report.CurrentDirectoryRoles -join ', ')" Add-Log "Missing roles: $($report.MissingDirectoryRoles -join ', ')" Add-Log "Proposed least-privilege role: $($report.ProposedLeastPrivilegeRole)" } Add-Log "Permission rescue complete." } catch { Add-Log "Permission rescue failed: $_" } }) $form.Add_Shown({ $form.Activate() }) [void]$form.ShowDialog() } |