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()
}