Om.Azure.Management.psm1
function ConvertFrom-OmResourceID { param ( [Parameter(Mandatory = $true)]$ResourceID, [switch]$GetSubscriptionName ) process { $ResourceIDSplit = [regex]::split($ResourceID, '\/|\\') $Subscription = $null $ManagementGroup = $null $ResourceGroup = $null $Resource = $null $returnObject = [PSCustomObject]@{ ResourceID = $ResourceID ScopeLevel = $null } for ($i = 0; $i -lt $ResourceIDSplit.Count; $i++) { switch ($ResourceIDSplit[$i]) { "ManagementGroups" { $ManagementGroup = $ResourceIDSplit[$i + 1] $returnObject | Add-Member -MemberType NoteProperty -Name ManagementGroup -Value $ManagementGroup $returnObject.scopelevel = "ManagementGroup" } "Subscriptions" { $Subscription = $ResourceIDSplit[$i + 1] $returnObject | Add-Member -MemberType NoteProperty -Name Subscription -Value $Subscription if ($GetSubscriptionName -and ($null -ne $Subscription)) { $SubscriptionName = (Get-AzSubscription -SubscriptionId $Subscription).Name $returnObject | Add-Member -MemberType NoteProperty -Name SubscriptionName -Value $SubscriptionName } $returnObject.scopelevel = "Subscription" } "ResourceGroups" { $ResourceGroup = $ResourceIDSplit[$i + 1] $returnObject | Add-Member -MemberType NoteProperty -Name ResourceGroup -Value $ResourceGroup $returnObject.scopelevel = "ResourceGroup" if ($null -ne $ResourceIDSplit[$i + 2]) { $Resource = $ResourceIDSplit[-1] $returnObject | Add-Member -MemberType NoteProperty -Name Resource -Value $Resource $returnObject.scopelevel = "Resource" } } } } return $returnObject } } function ConvertTo-OmResourceID { param ( [Parameter(ParameterSetName = 'ManagementGroup', Mandatory = $true)] [Alias('ManagementGroupName')] $ManagementGroup, [Parameter(ParameterSetName = 'Subscription', Mandatory = $true)] [Parameter(ParameterSetName = 'ResourceGroup', Mandatory = $true)] [Parameter(ParameterSetName = 'Resource', Mandatory = $true)] [Alias('Subscription')] $SubscriptionId, [Parameter(ParameterSetName = 'ResourceGroup', Mandatory = $true)] [Parameter(ParameterSetName = 'Resource', Mandatory = $true)] [Alias('ResourceGroup')] $ResourceGroupName, [Parameter(ParameterSetName = 'Resource', Mandatory = $true)] $Provider, [Parameter(ParameterSetName = 'Resource', Mandatory = $true)] [Alias('Resource')] $ResoureName ) switch ($PSCmdlet.ParameterSetName) { "ManagementGroup" { return "/providers/Microsoft.Management/managementGroups/$managementGroup" } "Subscription" { return "/subscriptions/$subscriptionId" } "ResourceGroup" { return "/subscriptions/$subscriptionId/resourcegroups/$resourcegroupname" } "Resource" { return"/subscriptions/$subscriptionId/resourcegroups/$resourcegroupname/providers/$provider/$resourceName" } } } function Get-OmAADObject { param( [parameter(Mandatory = $true)]$ObjectID ) # First check if the id refers to an user $AADObject = Get-AzADUser -ObjectId $ObjectID -ErrorAction Stop if ($null -eq $AADObject) { # If not: check if the id refers to a Service Principal $AADObject = Get-AzADServicePrincipal -ObjectId $ObjectID -ErrorAction Stop if ($null -eq $AADObject) { # If not: check if the id refers to a Group $AADObject = Get-AzADGroup -ObjectId $ObjectID -ErrorAction Stop } } return $AADObject } function Get-OmAADMGraphGroup { param( [Parameter(ParameterSetName = 'object', Mandatory = $true)] [Alias('object')] $objectID, [Parameter(ParameterSetName = 'name', Mandatory = $true)] [Alias('name')] $displayName, [Parameter(ParameterSetName = 'nameStartsWith', Mandatory = $true)] [Alias('nameStartsWith')] $displayNameStartsWith, [Parameter(Mandatory = $false)]$mgmtGroupsApiVersion = 'v1.0', [Parameter(Mandatory = $false)]$headers = (Get-OmAzureMGraphAuthHeader) ) switch ($PSCmdlet.ParameterSetName) { "object" { $graphUri = "https://graph.microsoft.com/$mgmtGroupsApiVersion/groups/$ObjectID" try { $response = Invoke-WebRequest -Headers $headers -Uri $graphUri -UseBasicParsing -Method "get" -ContentType "application/json" } catch { $response = Invoke-WebRequest -Headers $headers -Uri $graphUri -UseBasicParsing -Method "get" -ContentType "application/json" } $AADGroup = $response.Content | convertfrom-json return $AADGroup } "name" { $filter = "`$filter=displayName eq '$DisplayName'" } "nameStartsWith" { $filter = "`$filter=startswith(displayName,'$displayNameStartsWith')" } } $graphUri = "https://graph.microsoft.com/$mgmtGroupsApiVersion/groups?$($filter)" $AADObjectResults = [System.Collections.ArrayList]@() while ($graphUri) { $objectList = $null try { $objectList = Invoke-RestMethod -Headers $headers -Uri $graphUri -UseBasicParsing -Method "get" -ContentType "application/json" } catch { $objectList = Invoke-RestMethod -Headers $headers -Uri $graphUri -UseBasicParsing -Method "get" -ContentType "application/json" } $graphUri = $($objectList.'@odata.nextLink') if($null -ne $objectList.value -and $($objectList.value).count -gt 1){ $AADObjectResults.AddRange($objectList.value) } elseif($null -ne $objectList.value) { [void]$AADObjectResults.Add($objectList.value) } } return $AADObjectResults } function Expand-OmAADGroup { param ( [parameter(Mandatory = $true)]$ObjectID, [switch]$recurse ) $AADObjectList = New-Object System.Collections.Generic.List[System.Object] $groupName = (Get-azADGroup -objectid $objectID).DisplayName $group = Get-AzADGroupmember -objectid $ObjectID foreach ($member in $group) { $AADObject = get-OmAADObject -ObjectID $member.Id $AADObject | Add-Member -NotePropertyName "MemberOf" -NotePropertyValue $groupName [void]$AADObjectList.add($AADObject) if ($recurse -and $member.objecttype -eq "group") { $recurseList = expand-OmAADGroup -ObjectId $member.id -recurse foreach ($RecurseObject in $recurseList) { [void]$AADObjectList.add($RecurseObject) } } } return $AADObjectList } function Get-OmParentManagementGroup { param( [parameter(Mandatory = $true)] [Alias('SubscriptionID', 'Subscription', 'ManagementGroup', 'ManagementGroupID', 'ManagementGroupName')]$ID, [switch]$onlyName ) $managementgroups = Get-AzManagementGroup foreach ($managementgroup in $managementgroups) { $managementgroupExpanded = Get-AzManagementGroup -Expand -Groupid $managementgroup.name foreach ($child in $managementgroupExpanded.Children) { if ($child.name -eq $ID) { if ($onlyName) { return $managementgroup.name } return $managementgroupExpanded } } } throw "Could not find parent of $ID " } function Get-OmParentResourceID { param( [parameter(Mandatory = $true)]$ResourceID ) $scopeObject = ConvertFrom-OmResourceID $ResourceID switch ($scopeObject.scopelevel) { "ManagementGroup" { return (get-OmParentManagementGroup -ManagementGroupName $scopeObject.managementGroup).id } "Subscription" { return (get-OmParentManagementGroup -SubscriptionID $scopeObject.subscription).id } "ResourceGroup" { return ConvertTo-OmResourceID -SubscriptionID $scopeObject.subscription } "Resource" { return ConvertTo-OmResourceID -SubscriptionID $scopeObject.subscription -ResourceGroupName $scopeObject.resourcegroup } Default { Throw "Didn't find scopelevel for scope: $scope" } } } function Get-OmSubscriptionsFromMG { param( [parameter(Mandatory = $true)][Alias("Parents", "ManagementGroup", "ManagementGroups")]$Parent ) $mgs = [System.Collections.ArrayList]@() $subscriptions = [System.Collections.ArrayList]@() $parents = [System.Collections.ArrayList]@() if ($parent.count -eq 1) { $parents.Add($parent) | Out-Null } else { $parents.AddRange($parent) | Out-Null } try { $mgNames = (get-azManagementgroup -ErrorAction stop).Name } catch { write-host "Retrying get-azManagementgroup" $mgNames = (get-azManagementgroup -ErrorAction stop).Name } foreach ($mgName in $mgNames) { try { $null = $mgs.add($(get-azManagementGroup -GroupId $mgName -Expand -ErrorAction stop)) } catch { write-host "Retrying get-azManagementgroup for $mgName" $null = $mgs.add($(get-azManagementGroup $mgName -Expand -ErrorAction stop)) } } for ($i = 0; $i -lt $parents.Count; $i++) { $parent = $parents[$i] write-host $parent $parentMg = $mgs | Where-Object name -eq $parent [Array]$mgsubs = $ParentMg.Children | where-object Type -like "*subscription*" foreach ($sub in $mgSubs) { $sub | Add-Member -NotePropertyName "ManagementGroup" -NotePropertyValue $parent $null = $subscriptions.add($sub) } foreach ($mg in $mgs | where-object ParentName -eq $parent) { $parents.add($mg.name) | Out-Null } } return $subscriptions } function Get-OmRoleDefinitions { param ( [Parameter(Mandatory = $true)]$scope, [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader) ) $uri = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-07-01" $roleDefinitionResults = [System.Collections.ArrayList]@() do { try { $response = Invoke-WebRequest -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } catch { Write-Host "Retrying getting roleDefinitions" Start-Sleep 30 $response = Invoke-WebRequest -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } $roleDefinitionResultsChunk = ($response.Content | convertfrom-json).value if($null -ne $roleDefinitionResultsChunk -and $roleDefinitionResultsChunk.count -gt 1){ $roleDefinitionResults.AddRange($roleDefinitionResultsChunk) } elseif($null -ne $roleDefinitionResultsChunk) { [void]$roleDefinitionResults.Add($roleDefinitionResultsChunk) } $restUri = ($response.Content | convertfrom-json).nextLink } while ($restUri) return $roleDefinitionResults } function Get-OmMgDecendants { param( [Parameter(Mandatory = $true)]$managementGroup, [Parameter(Mandatory = $false)]$mgmtGroupsApiVersion = '2018-03-01-preview', [Parameter(Mandatory = $false)]$headers = (Get-OmAzureRMAuthHeader) ) $uri = "https://management.azure.com/providers/Microsoft.Management/managementGroups/$managementGroup/descendants?api-version=$mgmtGroupsApiVersion" $method = 'GET' $mgmtGroupChildren =[System.Collections.ArrayList]@() do{ try { $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method $method } catch { $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method $method } if($null -ne $response.value -and $response.value.count -gt 1){ $mgmtGroupChildren.AddRange($response.value) } elseif($null -ne $response.value) { [void]$mgmtGroupChildren.Add($response.value) } $uri = $response.nextLink } while ($uri) return $mgmtGroupChildren } function Get-OmMgSubscriptionDecendants { param( [Parameter(Mandatory = $true)]$managementGroup, [Parameter(Mandatory = $false)]$mgmtGroupsApiVersion = '2018-03-01-preview', [Parameter(Mandatory = $false)]$headers = (Get-OmAzureRMAuthHeader), [switch]$onlyEnabled ) $subscriptionDecendants = Get-OmMgDecendants -managementGroup $managementGroup -mgmtGroupsApiVersion $mgmtGroupsApiVersion -headers $headers | where-object {$_.type -eq "/subscriptions"} if (($onlyEnabled) -and ($subscriptionDecendants.count -gt 0)) { try { $azureSubscriptions = Get-AzSubscription -ErrorAction Stop } catch { $azureSubscriptions = Get-AzSubscription -ErrorAction Stop } $azureSubscriptions = $azureSubscriptions | Where-Object {$_.State -eq 'Enabled'} $subscriptionDecendants = $subscriptionDecendants | Where-Object {$_.name -in $azureSubscriptions.id } } return $subscriptionDecendants } function Get-OmMgManagementGroupDecendants { param( [Parameter(Mandatory = $true)]$managementGroup, [Parameter(Mandatory = $false)]$mgmtGroupsApiVersion = '2018-03-01-preview', [Parameter(Mandatory = $false)]$headers = (Get-OmAzureRMAuthHeader) ) return Get-OmMgDecendants -managementGroup $managementGroup -mgmtGroupsApiVersion $mgmtGroupsApiVersion -headers $headers | where-object {$_.type -eq "/providers/Microsoft.Management/managementGroups"} } function Get-OmAzureRMAuthHeader { $azContext = Get-AzContext $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile $profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($azProfile) $token = $profileClient.AcquireAccessToken($azContext.Subscription.TenantId) $authHeader = @{ 'Content-Type' = 'application/json' 'Authorization' = 'Bearer ' + $token.AccessToken } return $authHeader } function Get-OmAzureMGraphAuthHeader { param( [Parameter(ParameterSetName = 'context', Mandatory = $false)] [Alias('context')] $azContext = (Get-AzContext -ErrorAction Stop), [Parameter(ParameterSetName = 'client', Mandatory = $true)]$clientId, [Parameter(ParameterSetName = 'client', Mandatory = $true)]$clientSecret, [Parameter(ParameterSetName = 'client', Mandatory = $true)]$tenantId ) switch ($PSCmdlet.ParameterSetName) { "client" { $Scope = "https://graph.microsoft.com/.default" $UrlToken = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" $bodyToken = "grant_type=client_credentials&" + "client_id=" + $clientId + "&" + "scope=$Scope" + "&" + "client_secret=" + $clientSecret + "&" try { $response = Invoke-RestMethod -Body $bodyToken -Uri $urlToken -Method 'Post' -ContentType 'application/x-www-form-urlencoded' } catch { $response = Invoke-RestMethod -Body $bodyToken -Uri $urlToken -Method 'Post' -ContentType 'application/x-www-form-urlencoded' } $access_token = $response.access_token $headers = @{ 'Content-Type' = 'application/json' 'Authorization' = 'Bearer ' + $access_token } return $headers } "context" { $context = $azContext $graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.microsoft.com").AccessToken $headers = @{ 'Content-Type' = 'application/json' 'Authorization' = 'Bearer ' + $graphToken } return $headers } default { Throw "Failed. ParameterSetName not found" } } } ### Privileged Identity Management - Graph API function Get-OmPrivilegedAzureResource { param ( [Parameter(Mandatory = $true)]$externalId, [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureMGraphAuthHeader) ) $uri = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/resources?`$filter=externalId+eq+'$externalId'" try { $pimResource = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } catch { $pimResource = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } if($pimResource.value.count -gt 1) { return "Error, retrieved more than 1 resource:$($Results.value.Length)" } return $pimResource.value[0] } function Get-OmPrivilegedAzureResourcesRoleDefinitions { param ( [Parameter(Mandatory = $true)]$resourceId, [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureMGraphAuthHeader) ) $uri = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/resources/$($resourceId)/roleDefinitions" $roleDefResults = [System.Collections.ArrayList]@() while ($uri) { $roleDefChunk = $null try { $roleDefChunk = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } catch { $roleDefChunk = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } $uri = $($roleDefChunk.'@odata.nextLink') if($null -ne $roleDefChunk.value -and $($roleDefChunk.value).count -gt 1){ $roleDefResults.AddRange($roleDefChunk.value) } elseif($null -ne $roleDefChunk.value) { [void]$roleDefResults.Add($roleDefChunk.value) } } return $roleDefResults } function Get-OmPrivilegedAzureResourcesroleAssignments { param ( [Parameter(Mandatory = $true)]$resourceId, [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureMGraphAuthHeader) ) $uri = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/resources/$($resourceId)/roleAssignments" $roleAssignResults = [System.Collections.ArrayList]@() while ($uri) { $roleAssignChunk = $null try { $roleAssignChunk = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } catch { $roleAssignChunk = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } $uri = $($roleAssignChunk.'@odata.nextLink') if($null -ne $roleAssignChunk.value -and $($roleAssignChunk.value).count -gt 1){ $roleAssignResults.AddRange($roleAssignChunk.value) }elseif($null -ne $roleAssignChunk.value) { [void]$roleAssignResults.Add($roleAssignChunk.value) } } return $roleAssignResults } ### Privileged Identity Management - Management API function Get-OmPrivilegedRoleAssignments { param ( [Parameter(Mandatory = $true)]$scope, [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader), [switch]$atScope ) #$uriFilter = "principalId eq '$principalId'" if ($atScope) { $uriFilter = "atScope()" } $uri = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?`$filter=$($uriFilter)&api-version=2020-10-01-preview" $roleAssignResults = [System.Collections.ArrayList]@() do { try { $response = Invoke-WebRequest -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } catch { $response = Invoke-WebRequest -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" } $roleAssignResultsChunk = ($response.Content | convertfrom-json).value if($null -ne $roleAssignResultsChunk -and $roleAssignResultsChunk.count -gt 1){ $roleAssignResults.AddRange($roleAssignResultsChunk) } elseif($null -ne $roleAssignResultsChunk) { [void]$roleAssignResults.Add($roleAssignResultsChunk) } $restUri = ($response.Content | convertfrom-json).nextLink } while ($restUri) return $roleAssignResults } function New-OmPrivilegedRoleAssignment { param ( [Parameter(Mandatory = $true)]$scope, [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader), [Parameter(Mandatory = $true)]$startDateTime, [Parameter(Mandatory = $true)]$requestType, [Parameter(Mandatory = $true)]$principalId, [Parameter(Mandatory = $true)]$endDateTime, [Parameter(Mandatory = $true)]$roleDefinitionId, [Parameter(Mandatory = $false)]$justification ) $startDateTime = "{0:yyyy-MM-ddTHH:mm:ss.ffZ}" -f ($startDateTime).ToUniversalTime() $endDateTime = "{0:yyyy-MM-ddTHH:mm:ss.ffZ}" -f ($endDateTime).ToUniversalTime() $body = @" { "properties": { "justification": "$justification", "principalId": "$principalId", "roleDefinitionId": "$roleDefinitionId", "requestType": "$requestType", "scheduleInfo": { "startDateTime": "$startDateTime", "expiration": { "type": "AfterDateTime", "endDateTime": "$endDateTime" } }, "condition": "", "conditionVersion": "" } } "@ $roleEligibilityScheduleRequestName = $((New-Guid).guid) $uri = "https://management.azure.com/$($scope)/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/$($roleEligibilityScheduleRequestName)?api-version=2020-10-01-preview" try { $response = Invoke-WebRequest -Method PUT -Uri $uri -header $headers -Body $body } catch { Write-Host "Retrying creating role assignment" Start-Sleep 30 $response = Invoke-WebRequest -Method PUT -Uri $uri -header $headers -Body $body } if ($response.StatusCode -eq 201) { Write-Output "Code 201: Roleassignment Created" } else { Write-Output "Code $($response.StatusCode): Roleassignment was not created" } } function Search-OmResourceGraph { param ( [Parameter(Mandatory = $true)][Alias('query')]$queries, [Parameter(Mandatory = $true)][Alias('Subscription')]$Subscriptions, [Parameter(Mandatory = $false)]$throttleLimit = 100, [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader) ) $subscriptionGroups = [System.Collections.ArrayList]@() if ($Subscriptions.count -gt 1) { $numOfGroups = [int][Math]::Ceiling($Subscriptions.count / $throttleLimit) for ($i = 0; $i -lt $numOfGroups; $i++) { $start = $i * $throttleLimit $end = [Math]::Clamp(($i + 1) * $throttleLimit - 1, 0, $Subscriptions.count - 1) $subscriptionGroups.add($Subscriptions[$start..$end]) | Out-Null } } else { $subscriptionGroups.Add(@($Subscriptions)) | Out-Null } $queries | ForEach-Object { $query = $_ foreach ($subscriptionGroup in $subscriptionGroups) { $response = $null $Subscription = $SubscriptionGroup -join """,""" $body = @" { "subscriptions": [ "$Subscription"], "query": '$query', "options": { "ResultFormat": "objectArray" } } "@ $restUri = "https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2019-04-01" do { $response = $null try { $response = Invoke-webrequest -Uri $restUri -Method Post -Body $body -Headers $headers } catch { $response = Invoke-webrequest -Uri $restUri -Method Post -Body $body -Headers $headers } $responseData = $null $responseData = $response.Content | convertfrom-json -Depth 100 $responseData.data if (($responseData.resultTruncated -eq $true)) { Write-Warning "Query result for subscription $Subscription is truncated." } $body = @" { "subscriptions": [ "$Subscription"], "query": '$query', "options": { "`$skipToken": "$($responseData.'$skipToken')", "ResultFormat": "objectArray" } } "@ if ($null -ne $response.Headers) { # Need to wait if query quota is 0 $remainingQuota = $response.Headers["x-ms-user-quota-remaining"] if ($remainingQuota -eq 0) { $resetAfter = ([System.TimeSpan]::Parse($response.Headers["x-ms-user-quota-resets-after"])).TotalSeconds $resetAfter++ Write-Host "Hitting Resource Graph quota limit. Waiting $resetAfter seconds" Start-Sleep $resetAfter } } } While ($responseData.'$skipToken') Start-Sleep 3 } } } |