PIM.Graph.psm1
function Invoke-PimGraphRequest { <# .SYNOPSIS Execute a graph request. .DESCRIPTION Execute a graph request. Wrapper command around Invoke-MgGraphRequest with better output processing. .PARAMETER Uri Relative link to call. Passed through to Invoke-MgGraphRequest. If no 'beta' or 'v1.0' prefix is used, it automatically injects 'v1.0' .PARAMETER Method What REST Method to call. Defaults to GET. .PARAMETER Body A body to pass to the request. .EXAMPLE PS C:\> Invoke-PimGraphRequest me Retrieves information about the current user. #> [CmdletBinding()] param ( [string] $Uri, [string] $Method = 'GET', [hashtable] $Body ) if ($Uri -notmatch '^v1.0/|^beta/') { $Uri = 'v1.0/{0}' -f $Uri } $param = @{ Method = $Method ErrorAction = 'Stop' } if ($Body) { $param.Body = $Body } $nextLink = $Uri while ($nextLink) { $result = Invoke-MgGraphRequest @param -Uri $nextLink if ($result.value) { foreach ($entry in $result.value) { if ($entry -isnot [hashtable]) { $entry } else { [PSCustomObject]$entry } } } elseif ($result.Keys.Count -eq 2 -and $result.Keys -contains 'value') { # Do nothing, there are no results } else { if ($result -isnot [Hashtable]) { $result } else { [PSCustomObject]$result } } $nextLink = $result.'@odata.nextlink' -replace '^https://graph.microsoft.com/' } } function Resolve-User { <# .SYNOPSIS Resolves a user into an ID .DESCRIPTION Resolves a user into an ID .PARAMETER Identity ID or UPN or mail of the user to resolve. .PARAMETER Me Whether to retrieve the ID of the current user. .EXAMPLE PS C:\> Resolve-User -Me Retrieve the ID of the current user .EXAMPLE PS C:\> Resolve-User -Identity max.mustermann@contoso.com Retrieve the ID of max.mustermann@contoso.com #> [OutputType([string])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Identity')] [string] $Identity, [Parameter(Mandatory = $true, ParameterSetName = 'Me')] [switch] $Me ) process { if ($Me) { try { (Invoke-PimGraphRequest -Uri 'me' -ErrorAction Stop).Id } catch { $PSCmdlet.ThrowTerminatingError($_) } return } if ($Identity -as [guid]) { return $Identity } try { $user = Invoke-PimGraphRequest -Uri "users?`$select=id&`$filter=userPrincipalName eq '$Identity' or mail eq '$Identity'" -ErrorAction Stop } catch { $PSCmdlet.ThrowTerminatingError($_) } if (-not $user) { throw "User not found: $user" } $user.id } } function Enable-PIMRole { <# .SYNOPSIS Activate a temporary Role membership. .DESCRIPTION Activate a temporary Role membership. Scopes Needed: RoleAssignmentSchedule.ReadWrite.Directory .PARAMETER Role The role to activate. .PARAMETER TicketNumber The ticket number associated with the privilege activation. .PARAMETER Reason The reason you require the role to be activated .PARAMETER Duration For how long the role should be active. Must be at least 5 minutes, maximum duration is defined in PIM. Defaults to 8 hours. .PARAMETER StartTime When the activation should start. Defaults to "now" .PARAMETER TicketSystem What ticket system is associated with the ticket number offered. Defaults to 'N/A' .PARAMETER DirectoryScope What scope the the activation applies to. Defaults to '/'. .EXAMPLE PS C:\> Enable-PIMRole 'Global Administrator' '#1234' 'Updating global tenant settings.' Enables the 'Global Administrator' role for 8 hours. .LINK https://learn.microsoft.com/en-us/graph/api/rbacapplication-post-roleassignmentschedulerequests?view=graph-rest-1.0&tabs=http #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $Role, [Parameter(Mandatory = $true)] [string] $TicketNumber, [Parameter(Mandatory = $true)] [string] $Reason, [timespan] $Duration = "08:00:00", [datetime] $StartTime = (Get-Date), [string] $TicketSystem = "N/A", [string] $DirectoryScope = "/" ) process { $resolvedRole = Resolve-PIMRole -Identity $Role $body = @{ action = "SelfActivate" principalId = (Invoke-MgGraphRequest -Uri "v1.0/me").id roleDefinitionId = $resolvedRole directoryScopeId = $DirectoryScope justification = $Reason scheduleInfo = @{ startDateTime = $StartTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') expiration = @{ type = "AfterDuration" duration = "PT$($Duration.TotalMinutes)M" } } ticketInfo = @{ ticketNumber = $TicketNumber ticketSystem = $TicketSystem } } try { Invoke-PimGraphRequest -Uri "v1.0/roleManagement/directory/roleAssignmentScheduleRequests" -Method POST -Body $body -ErrorAction Stop } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-PIMRole { <# .SYNOPSIS Search AAD for directory roles. .DESCRIPTION Search AAD for directory roles. Scopes: RoleManagement.Read.Directory, Directory.Read.All, RoleManagement.ReadWrite.Directory, Directory.ReadWrite.All .PARAMETER Name The name to filter the roles by. .EXAMPLE PS C:\> Get-PIMRole Retrieve all active roles. #> [CmdletBinding()] Param ( [string] $Name = '*' ) process { Invoke-PimGraphRequest -Uri "v1.0/directoryRoles" | Where-Object displayName -Like $Name } } function Get-PIMRoleAssignment { <# .SYNOPSIS Retrieve permanent role assignments. .DESCRIPTION Retrieve permanent role assignments. Scopes Needed: RoleManagement.Read.Directory .PARAMETER Role Role for which to find assignees. .PARAMETER User User for which to retrieve assignments. Specify either "me" for the current user or UPN/mail of specific user. .EXAMPLE PS C:\> Get-PIMRoleAssignment Retrieve ALL role assignments. .EXAMPLE PS C:\> Get-PIMRoleAssignment -User me Retrieve all role assignments of the current user. .EXAMPLE PS C:\> Get-PIMRoleAssignment -Role 'Global Administrator' Retrieve all memberships in the 'Global Administrator' role. .LINK https://learn.microsoft.com/en-us/graph/api/rbacapplication-list-roleassignments?view=graph-rest-1.0&tabs=http #> [CmdletBinding()] param ( [string] $Role, [string] $User ) begin { $filterSegments = @() if ($Role) { $roleID = Resolve-PIMRole -Identity $Role $filterSegments += "roleDefinitionId eq '$roleID'" } if ($User) { if ('me' -eq $User) { $userID = Resolve-User -Me } else { $userID = Resolve-User -Identity $User } $filterSegments += "principalId eq '$userID'" } $filterString = '' if ($filterSegments) { $filterString = '&$filter={0}' -f ($filterSegments -join ' and ') } } process { $results = Invoke-PimGraphRequest -Uri "v1.0/roleManagement/directory/roleAssignments?`$expand=principal$filterString" foreach ($result in $results) { [PSCustomObject]@{ # General Info RoleID = $result.roleDefinitionId PrincipalID = $result.principalId DirectoryScope = $result.directoryScopeId # Principal Details PrincipalName = $result.principal.displayName PrincipalType = $result.principal.'@odata.type' -replace '#microsoft\.graph\.' # Assignment data AssignmentID = $result.id Principal = $result.principal } } } } function Get-PIMRoleRequest { <# .SYNOPSIS Retrieve previously submitted role elevation requests. .DESCRIPTION Retrieve previously submitted role elevation requests. Returns both requests created in the Portal and those created by commandline. Scopes needed (least to most privileged): RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, RoleManagement.Read.All, RoleEligibilitySchedule.ReadWrite.Directory, RoleManagement.ReadWrite.Directory .PARAMETER Role Role for which to retrieve elevation requests. .PARAMETER User User for which to retrieve elevation requests .EXAMPLE PS C:\> Get-PIMRoleRequest -User me Retrieve all requests for the current account. .EXAMPLE PS C:\> Get-PIMRoleRequest -Role 'Global Administrator' Retrieve all requests for Global Admin .LINK https://learn.microsoft.com/en-us/graph/api/rbacapplication-list-roleeligibilityschedulerequests?view=graph-rest-1.0&tabs=http #> [CmdletBinding()] param ( [string] $Role, [string] $User ) begin { function Get-ExpirationTime { [CmdletBinding()] param ( $ScheduleInfo ) if ($ScheduleInfo.expiration.endDateTime) { return $ScheduleInfo.expiration.endDateTime } $start = $ScheduleInfo.startDateTime $duration = $ScheduleInfo.expiration.duration -replace '^PT' $end = $start $minutes = $duration -replace '^.{0,}?(\d+)M.{0,}$', '$1' if ($minutes -and $minutes -ne $duration) { $end = $end.AddMinutes($minutes) } $hours = $duration -replace '^.{0,}?(\d+)H.{0,}$', '$1' if ($hours -and $hours -ne $duration) { $end = $end.AddHours($hours) } $end } $filterSegments = @() if ($Role) { $roleID = Resolve-PIMRole -Identity $Role $filterSegments += "roleDefinitionId eq '$roleID'" } if ($User) { if ('me' -eq $User) { $userID = Resolve-User -Me } else { $userID = Resolve-User -Identity $User } $filterSegments += "principalId eq '$userID'" } $filterString = '' if ($filterSegments) { $filterString = '&$filter={0}' -f ($filterSegments -join ' and ') } } process { $requests = Invoke-PimGraphRequest -Uri "roleManagement/directory/roleAssignmentScheduleRequests?`$expand=principal$($filterString)" foreach ($request in $requests) { [PSCustomObject]@{ PSTypeName = 'PIM.Graph.RoleRequest' # IDs RequestID = $request.id PrincipalID = $request.principalId RoleID = $request.roleDefinitionId # State Action = $request.action Status = $request.status # Schedule Start = $request.scheduleInfo.startDateTime End = Get-ExpirationTime -ScheduleInfo $request.scheduleInfo ExpirationType = $request.scheduleInfo.expiration.type ExpirationDuration = $request.scheduleInfo.expiration.duration ExpirationTime = $request.scheduleInfo.expiration.endDateTime Created = $request.createdDateTime Completed = $request.completedDateTime # Metadata Reason = $request.justification TicketNumber = $request.ticketInfo.ticketNumber TicketSystem = $request.ticketInfo.ticketSystem # Principal PrincipalType = $request.principal.'@odata.type' -replace '#microsoft\.graph\.' PrincipalName = $request.principal.displayName PrincipalUPN = $request.principal.userPrincipalName # Role Role = Resolve-PIMRole -Identity $request.roleDefinitionId -AsName -Lenient Data = $request } } } } function Resolve-PIMRole { <# .SYNOPSIS Resolve a role by ID or name. .DESCRIPTION Resolve a role by ID or name. Uses role providers to do the resolving with, some of which are provided out of the box: - builtin: Provides the default IDs for the builtin roles (such as Global Administrator) - manual: Allows manually mapping name to ID using Set-PIMRoleMapping. - Get-PIMRole: Uses Get-PIMRole to retrieve active roles from Azure AD. This requires having the correct scopes and permissions to retrieve them. For more details on Role Providers, see the following commands: - Get-PIMRoleProvider: List available Role Providers. - Set-PIMRoleProvider: Modify existing Role Providers (most notably: Disable or enable) - Register-PIMRoleProvider: Create a new Role Provider - Unregister-PIMRoleProvider: Remove an existing Role Provider .PARAMETER Identity Role to resolve. .PARAMETER AsName Resolve to name rather than ID. .PARAMETER Lenient In case of not finding anything, return the specified Identity, rather than throwing an exception. .EXAMPLE PS C:\> Resolve-PIMRole -Identity 'Global Administrator' Returns the ID of the Global Administrator role. #> [OutputType([string])] [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $Identity, [switch] $AsName, [switch] $Lenient ) process { # If no resolution is required and ID is provided, return ID if (-not $AsName -and $Identity -as [Guid]) { return $Identity } $providers = Get-PIMRoleProvider -Enabled | Sort-Object Priority foreach ($provider in $providers) { Write-Verbose "Resolving $Identity through $($provider.Name)" try { $result = & $provider.Conversion $Identity $AsName if ($result) { return $result } } catch { Write-Verbose "Error resolving $Identity through $($provider.Name): $_" } } if ($Lenient) { return $Identity } throw "Unable to resolve $Identity" } } function Stop-PIMRoleRequest { <# .SYNOPSIS Cancels a pending role request. .DESCRIPTION Cancels a pending role request. Scopes needed (least to most privileged): RoleEligibilitySchedule.ReadWrite.Directory, RoleManagement.ReadWrite.Directory .PARAMETER ID ID of the request to cancel. .EXAMPLE PS C:\> Get-PIMRoleRequest -User me | Where-Object Status -eq Granted | Stop-PIMRoleRequest Cancels all role requests still pending for the current user .LINK https://learn.microsoft.com/en-us/graph/api/unifiedroleeligibilityschedulerequest-cancel?view=graph-rest-1.0&tabs=http #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('RequestID')] [string[]] $ID ) process { foreach ($requestID in $ID) { try { Invoke-PimGraphRequest -Method POST -Uri "v1.0/roleManagement/directory/roleAssignmentScheduleRequests/$requestID/cancel" -ErrorAction Stop } catch { $PSCmdlet.WriteError($_) } } } } function Get-PIMRoleProvider { <# .SYNOPSIS Lists all registered Role Providers. .DESCRIPTION Lists all registered Role Providers. Role Providers are plugins that allow resolving role names using the logic provided within. .PARAMETER Name Name of the Role Provider to retrieve. Defaults to '*' .PARAMETER Enabled Only return enabled Role Providers. .EXAMPLE PS C:\> Get-PIMRoleProvider Lists all registered Role Providers. .EXAMPLE PS C:\> Get-PIMRoleProvider -Enabled Lists all enabled Role Providers. #> [CmdletBinding()] Param ( [string] $Name = '*', [switch] $Enabled ) process { $enabledSet = $PSBoundParameters.ContainsKey('Enabled') ($script:roleProviders.Values) | Where-Object { $_.Name -Like $Name -and (-not $enabledSet -or $_.Enabled -eq $Enabled) } } } function Register-PIMRoleProvider { <# .SYNOPSIS Register a new Role Provider. .DESCRIPTION Register a new Role Provider. Role Providers are plugins that allow resolving role names using the logic provided within. .PARAMETER Name Name of the provider to create. Must be unique, otherwise it will overwrite an existing Provider. .PARAMETER Conversion Logic that processes input into results. The scriptblock must accept two parameters: - Identity - AsName Identity is the string input to convert. AsName is a boolean, whether to return the displayname of a role. By default, this scriptblock should be returning the ID. .PARAMETER ListNames A logic that, without any input, should return a list of role names. This is used for tab completion and you may leave this empty. Try to avoid including long-running logic or implement caching. .PARAMETER Description Description of the Role Provider. Used to give the user some impression of what and how it does. .PARAMETER Priority The priority of the Role Provider. The lower the number, the earlier it is executed. The first successful role resolution wins, causing Role Providers with a higher number to be skipped. Slower Role Providers should usually have a higher number. Defaults to 50. .PARAMETER Enabled Whether the Role Provider should be enabled. Only enabled Providers are used when resolving a role. Defaults to $true .EXAMPLE PS C:\> Resolve-PIMRoleProvider -Name 'custom-DB' -Conversion $conversion -ListNames { } -Priority 40 Registers the 'custom-DB' Role Provider with the specified conversion logic, an empty name listing logic and priority 40. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [scriptblock] $Conversion, [Parameter(Mandatory = $true)] [scriptblock] $ListNames, [string] $Description, [int] $Priority = 50, [bool] $Enabled = $true ) $script:roleProviders[$Name] = [PSCustomObject]@{ PSTypeName = 'PIM.Graph.RoleProvider' Name = $Name Conversion = $Conversion ListNames = $ListNames Priority = $Priority Enabled = $Enabled Description = $Description } } function Set-PIMRoleMapping { <# .SYNOPSIS Maps a role name to a role ID. .DESCRIPTION Maps a role name to a role ID. This allows manually defining how a name should be resolved, enabling ... - Role resolution without any scopes / connection required. - Defining aliases / shortcuts for frequently resolved roles .PARAMETER Name Name of the role. May either be the full name or an abbreviation as desired. .PARAMETER ID ID the name maps to. .PARAMETER Register Whether the mapping should be remembered across sessions. .EXAMPLE PS C:\> Set-PIMRoleMapping -Name GA -ID 62e90394-69f5-4237-9190-012177145e10 -Register Creates a permanent role name alias for the Global Administrator #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [string] $ID, [switch] $Register ) process { $script:manuallyMappedRoles[$ID] = $Name if (-not $Register) { return } $folder = Join-Path $env:APPDATA 'PowerShell/PIM.Graph' if (-not (Test-Path -Path $folder)) { $null = New-Item -Path $folder -Force -ItemType Directory } $rolesPath = "$folder/roles.clixml" if (Test-Path -Path $rolesPath) { $roles = Import-Clixml -Path $rolesPath } else { $roles = @{ } } $roles[$ID] = $Name $roles | Export-Clixml -Path $rolesPath } } function Set-PIMRoleProvider { <# .SYNOPSIS Modifies an existing Role Provider. .DESCRIPTION Modifies an existing Role Provider. Role Providers are plugins that allow resolving role names using the logic provided within. .PARAMETER Name Name of the Provider to modify. .PARAMETER Conversion The conversion logic that reslves names to ID. .PARAMETER ListNames The logic listing all available names for tab completion purposes. .PARAMETER Priority The priority of the Role Provider. The lower the number, the earlier it is executed. The first successful role resolution wins, causing Role Providers with a higher number to be skipped. Slower Role Providers should usually have a higher number. .PARAMETER Enabled Whether the Role Provider should be enabled. Only enabled Providers are used when resolving a role. .EXAMPLE PS C:\> Set-PIMRoleProvider -Name Get-PIMRole -Enabled $false Disables the Role Provider 'Get-PIMRole' #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string[]] $Name, [scriptblock] $Conversion, [scriptblock] $ListNames, [int] $Priority, [bool] $Enabled ) process { foreach ($providerName in $Name) { $provider = $script:roleProviders[$providerName] if (-not $provider) { Write-Error "Provider not found: $providerName" continue } if ($Conversion) { $provider.Conversion = $Conversion } if ($ListNames) { $provider.ListNames = $ListNames } if ($PSBoundParameters.ContainsKey('Priority')) { $provider.Priority = $Priority } if ($PSBoundParameters.ContainsKey('Enabled')) { $provider.Enabled = $Enabled } } } } function Unregister-PIMRoleProvider { <# .SYNOPSIS Remove an existing Role Provider. .DESCRIPTION Remove an existing Role Provider. Role Providers are plugins that allow resolving role names using the logic provided within. .PARAMETER Name Name of the Role Provider to remove .EXAMPLE PS C:\> Unregister-PIMRoleProvider -Name Get-PIMRole Removes the 'Get-PIMRole' Role Provider #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string[]] $Name ) process { foreach ($providerName in $Name) { $script:roleProviders.Remove($providerName) } } } # Registered role providers, logic resolving and listing roles $script:roleProviders = @{ } # Roles that come with every tenant. Their roleTemplateId is global across all tenants. $script:defaultBuiltinRoles = @{ '62e90394-69f5-4237-9190-012177145e10' = 'Global Administrator' 'd29b2b05-8046-44ba-8758-1e26182fcf32' = 'Directory Synchronization Accounts' '88d8e3e3-8f55-4a1e-953a-9b9898b8876b' = 'Directory Readers' 'e6d1a23a-da11-4be4-9570-befc86d067a7' = 'Compliance Data Administrator' 'f28a1f50-f6e7-4571-818b-6a12f2af6b6c' = 'SharePoint Administrator' '5d6b6bb7-de71-4623-b4af-96380a352509' = 'Security Reader' '69091246-20e8-4a56-aa4d-066075b2a7a8' = 'Teams Administrator' 'f70938a0-fc10-4177-9e90-2178f8765737' = 'Teams Communications Support Engineer' '2b499bcd-da44-4968-8aec-78e1674fa64d' = 'Device Managers' '194ae4cb-b126-40b2-bd5b-6091b380977d' = 'Security Administrator' 'baf37b3a-610e-45da-9e62-d9d1e5e8914b' = 'Teams Communications Administrator' '17315797-102d-40b4-93e0-432062caca18' = 'Compliance Administrator' 'f2ef992c-3afb-46b9-b7cf-a126ee74c451' = 'Global Reader' '29232cdf-9323-42fd-ade2-1d097af3e4de' = 'Exchange Administrator' 'a9ea8996-122f-4c74-9520-8edcd192826c' = 'Power BI Administrator' '9360feb5-f418-4baa-8175-e2a00bac4301' = 'Directory Writers' } # Mapping of manually defined roles $script:manuallyMappedRoles = @{ } $manualRolesPath = Join-Path $env:APPDATA 'PowerShell/PIM.Graph/roles.clixml' if (Test-Path $manualRolesPath) { try { $script:manuallyMappedRoles = Import-Clixml -Path $manualRolesPath -ErrorAction Stop } catch { Write-Warning "Error loading roles mapping configuration file. File may be corrupt. Delete or repair the file. Path: $manualRolesPath" } } $conversion = { param ( $Identity, $AsName ) foreach ($pair in $script:defaultBuiltinRoles.GetEnumerator()) { if ($AsName) { if ($pair.Key -eq $Identity) { return $pair.Value } } else { if ($pair.Value -eq $Identity) { return $pair.Key } } } } $listnames = { $script:defaultBuiltinRoles.Values } $param = @{ Name = 'builtin' Conversion = $conversion ListNames = $listnames Priority = 2 Enabled = $true Description = 'A static mapping of the common builtin roles.' } Register-PIMRoleProvider @param $conversion = { param ( $Identity, $AsName ) $roles = Get-PIMRole if ($AsName) { $roles | Where-Object { $_.Id -eq $Identity -or $_.roleTemplateId -eq $Identity } | Select-Object -First 1 | ForEach-Object displayName } else { $roles | Where-Object displayName -EQ $Identity | Select-Object -First 1 | ForEach-Object displayName } } $listnames = { (Get-PIMRole).displayName } $param = @{ Name = 'Get-PIMRole' Conversion = $conversion ListNames = $listnames Priority = 60 Enabled = $true Description = 'Uses Get-PIMRole to resolve roles against graph. Requires scope RoleManagement.Read.Directory' } Register-PIMRoleProvider @param $conversion = { param ( $Identity, $AsName ) foreach ($pair in $script:manuallyMappedRoles.GetEnumerator()) { if ($AsName) { if ($pair.Key -eq $Identity) { return $pair.Value } } else { if ($pair.Value -eq $Identity) { return $pair.Key } } } } $listnames = { $script:manuallyMappedRoles.Values } $param = @{ Name = 'manual' Conversion = $conversion ListNames = $listnames Priority = 1 Enabled = $true Description = 'Allows manually defining a name-to-role mapping using Set-PIMRoleMapping.' } Register-PIMRoleProvider @param #region Role Names $completion = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $providers = Get-PIMRoleProvider -Enabled $names = foreach ($provider in $providers) { & $provider.ListNames } $names | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { if ($_ -match "\s") { "'$_'" } else { $_ } } } Register-ArgumentCompleter -CommandName Resolve-PIMRole -ParameterName Identity -ScriptBlock $completion Register-ArgumentCompleter -CommandName Enable-PIMRole -ParameterName Role -ScriptBlock $completion Register-ArgumentCompleter -CommandName Get-PIMRoleAssignment -ParameterName Role -ScriptBlock $completion #endregion Role Names #region Role Provider $completion = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) (Get-PIMRoleProvider -Name "$wordToComplete*").Name | ForEach-Object { if ($_ -match "\s") { "'$_'" } else { $_ } } } Register-ArgumentCompleter -CommandName Get-PIMRoleProvider -ParameterName Name -ScriptBlock $completion Register-ArgumentCompleter -CommandName Set-PIMRoleProvider -ParameterName Name -ScriptBlock $completion Register-ArgumentCompleter -CommandName Unregister-PIMRoleProvider -ParameterName Name -ScriptBlock $completion #endregion Role Provider |