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)
    )
    $filter = $null
    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 Get-OmAADMGraphServicePrincipal {
    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)
    )
    $filter = $null
    switch ($PSCmdlet.ParameterSetName) {
        "object" {
            $graphUri = "https://graph.microsoft.com/$mgmtGroupsApiVersion/serviceprincipals/$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/serviceprincipals?$($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-OmRoleAssignments {
    param (
        [Parameter(Mandatory = $true)]$scope,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader),
        [Parameter(Mandatory = $false)][string]$ApiVersion = '2015-07-01',
        [Parameter(ParameterSetName = 'principalFilter', Mandatory = $false)][string]$principalId,
        [switch]$atScope
    )
    # Will add filter if $atscope switch is set, or $principalId is populated, or both.
    # Filter is constructed based on https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-list-rest
    $filterList = @()
    if($atScope) {
        $filterList += "atScope()"
    }
    if($PSCmdlet.ParameterSetName -eq "principalFilter"){
        $filterList += "assignedTo('$principalId')"
    }
    $filter = if($filterList.count -gt 0){
        '$filter=' + $($filterList -join "+and+") + "&" 
    } else {
        ""
    }

    $uri = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleAssignments?${filter}api-version=${ApiVersion}"

    Get-OmAzureRMList -uri $uri -headers $headers
}

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" {
            $graphToken = (get-azAccessToken -ResourceUrl "https://graph.microsoft.com/").token
            $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
        $roleAssignChunk = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" -MaximumRetryCount 2 -RetryIntervalSec 1
        $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-OmPrivilegedEligibleRoleAssignments {
    param (
        [Parameter(Mandatory = $true)]$scope,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader),
        [switch]$atScope
    )
    $uriFilter = $null
    if ($atScope) {
        $uriFilter = "atScope()" 
    }
    $uri = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleEligibilitySchedules?`$filter=$($uriFilter)&api-version=2020-10-01-preview"

    Get-OmAzureRMList -uri $uri -headers $headers
}
function Get-OmPrivilegedEligibleRoleAssignmentInstances {
    param (
        [Parameter(Mandatory = $true)]$scope,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader),
        [switch]$atScope
    )
    $uriFilter = $null
    if ($atScope) {
        $uriFilter = "atScope()" 
    }
    $uri = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?`$filter=$($uriFilter)&api-version=2020-10-01-preview"

    Get-OmAzureRMList -uri $uri -headers $headers
}

function Get-OmPrivilegedRoleAssignments {
    param (
        [Parameter(Mandatory = $true)]$scope,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader),
        [switch]$atScope
    )
    $uriFilter = $null
    if ($atScope) {
        $uriFilter = "atScope()" 
    }
    $uri = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleAssignmentSchedules?`$filter=$($uriFilter)&api-version=2020-10-01-preview"
    Get-OmAzureRMList -uri $uri -headers $headers
}

function Get-OmPrivilegedRoleAssignmentInstances {
    param (
        [Parameter(Mandatory = $true)]$scope,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader),
        [switch]$atScope
    )
    $uriFilter = $null
    if ($atScope) {
        $uriFilter = "atScope()" 
    }
    $uri = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleAssignmentScheduleInstances?`$filter=$($uriFilter)&api-version=2020-10-01-preview"
    Get-OmAzureRMList -uri $uri -headers $headers
}

function New-OmPrivilegedEligibleRoleAssignment {
    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 Get-OmAzureRMList {
    param (
        [Parameter(Mandatory = $true)]$uri,
        [Parameter(Mandatory = $false)][Alias("header")]$headers
    )

    $resultList = [System.Collections.ArrayList]@()
    do {
        $response = Invoke-WebRequest -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json" -MaximumRetryCount 3 -RetryIntervalSec 1
        $chunk = ($response.Content | convertfrom-json).value
        if($null -ne $chunk -and $chunk.count -gt 1){
            $resultList.AddRange($chunk)
        } elseif($null -ne $chunk) {
            [void]$resultList.Add($chunk) 
        }
        $restUri = ($response.Content | convertfrom-json).nextLink
    } while ($restUri)

    return $resultList
}

####

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
    }
    $subscriptionGroupsCount = $subscriptionGroups.Count
    $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')
            if ($subscriptionGroupsCount -gt 1) {
                Start-Sleep 3
            }
        } 
    }
}

# Function to retrieve log analytics workspace secret
function Get-OmSharedKey {
    param (
        [Parameter(Mandatory = $true, ParameterSetName = "keyvault")]$service_principal_appid,
        [Parameter(Mandatory = $true, ParameterSetName = "keyvault")]$service_principal_secret_name,
        [Parameter(Mandatory = $true, ParameterSetName = "keyvault")]$keyvault_name,
        [Parameter(Mandatory = $true, ParameterSetName = "keyvault")]$log_workspace_secret_name,
        [Parameter(Mandatory = $true, ParameterSetName = "keyvault")]$tenantId,
        [Parameter(Mandatory = $false, ParameterSetName = "api")]$subscriptionId,
        [Parameter(Mandatory = $true, ParameterSetName = "api")]$resourceGroup,
        [Parameter(Mandatory = $true, ParameterSetName = "api")]$workspacename
    )
    if($PSCmdlet.ParameterSetName -eq "keyvault"){
        if ($((Get-AzContext).Account.Id) -ne $service_principal_appid ){
            try {
                $client_secret = (Get-AzKeyVaultSecret -vaultName $keyvault_name -name $service_principal_secret_name).SecretValue
                $pscredential = New-Object System.Management.Automation.PSCredential($service_principal_appid, $client_secret)
                $null = Connect-AzAccount -ServicePrincipal -Credential $pscredential -Tenant $tenantId
            }
            catch {
                Write-Error -Message $_.Exception
                throw $_.Exception
            }
        }
        $SharedKey = (Get-AzKeyVaultSecret -vaultName $keyvault_name -name $log_workspace_secret_name).secretValueText
    } elseif ($PSCmdlet.Parametersetname -eq "api") {
        if(!$subscriptionId){
            $subscriptionId = (get-azContext).Subscription.id
        }
        $uri ="https://management.azure.com/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/Microsoft.OperationalInsights/workspaces/$workspaceName/sharedKeys?api-version=2020-08-01"
        $method = "POST"
        $headers = Get-OmAzureRMAuthHeader
        $SharedKeys = Invoke-RestMethod -Uri $uri -Headers $headers -Method $method
        $SharedKey = $SharedKeys.primarySharedKey
    }

    return $SharedKey
}

# Function to create the authorization signature used in au authentication with log analytics
Function Build-OmSignature {
    param (
        [Parameter(Mandatory = $true)]$workspaceId,
        [Parameter(Mandatory = $true)]$sharedKey,
        [Parameter(Mandatory = $true)]$date,
        [Parameter(Mandatory = $true)]$contentLength,
        [Parameter(Mandatory = $true)]$method,
        [Parameter(Mandatory = $true)]$contentType,
        [Parameter(Mandatory = $true)]$resource
    )
    $xHeaders = "x-ms-date:" + $date
    $stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource

    $bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
    $keyBytes = [Convert]::FromBase64String($sharedKey)

    $sha256 = New-Object System.Security.Cryptography.HMACSHA256
    $sha256.Key = $keyBytes
    $calculatedHash = $sha256.ComputeHash($bytesToHash)
    $encodedHash = [Convert]::ToBase64String($calculatedHash)
    $authorization = 'SharedKey {0}:{1}' -f $workspaceId,$encodedHash
    return $authorization
}
# Function to create and post output data to log analytics
Function Post-OmLogAnalyticsData {
    param (
        [Parameter(Mandatory = $true)]$workspaceId,
        [Parameter(Mandatory = $true)]$sharedKey,
        [Parameter(Mandatory = $true)]$body,
        [Parameter(Mandatory = $true)]$tableName
    )
    $method = "POST"
    $contentType = "application/json"
    $resource = "/api/logs"
    $rfc1123date = [DateTime]::UtcNow.ToString("r")
    $contentLength = $body.Length
    $signature = Build-OmSignature `
        -workspaceId $workspaceId `
        -sharedKey $sharedKey `
        -date $rfc1123date `
        -contentLength $contentLength `
        -method $method `
        -contentType $contentType `
        -resource $resource
    $uri = "https://" + $workspaceId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"

    $headers = @{
        "Authorization" = $signature;
        "Log-Type" = $tableName;
        "x-ms-date" = $rfc1123date;
    }

    $response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
    return $response
}

function Get-OmJobOutput {
    param (
        [Parameter(Mandatory = $true)]$jobList,
        [Parameter(Mandatory = $false)]$jobNames,
        [Parameter(Mandatory = $false)]$PrintJobStatesTime = 30,
        [Parameter(Mandatory = $false)]$jobIntro,
        [Parameter(Mandatory = $false)]$jobAppendix,
        [switch]$finallyListJobs,
        [switch]$returnOutput,
        [Parameter(Mandatory = $false)]$sleepBetweenJobs = 1000
    )
    # Checking the state of each child job until the main job is in one of the following states
    $States = @('Completed', 'Failed', 'Stopped', 'Blocked', 'Suspended', 'Disconnected')
    $printTime = (Get-Date).AddMinutes($PrintJobStatesTime)
    $firstJobID = $jobList.ChildJobs[0].id

    $returnList = @()
    while ($jobList.State -notin $States) {
        Start-Sleep 5
        if ($(Get-Date) -gt $printTime) {
            $jobList.ChildJobs  | Format-Table -AutoSize | Out-String -Width 4096 | Out-Host
            $printTime = $printTime.AddMinutes($PrintJobStatesTime)
        }
        $jobList.ChildJobs | ForEach-Object {
            $child = $_
            # Writing output from each job when it is finished
            if ($child.State -in $States) {
                if ($($child.HasMoreData)) {
                    Start-Sleep -Milliseconds $sleepBetweenJobs
                    $jobName = if ($jobNames.count -gt 1){$($jobNames[$child.Id - $firstJobID])} else {$jobNames}
                    $stateColor = switch ($child.State) {
                        "Completed" { "`e[32m" }
                        "Failed" { "`e[31m" }
                        Default { "`e[33m" }
                    }
                    [System.Console]::WriteLine($(($jobIntro -replace "<StateColor>", $stateColor) -replace "<JobName>",$jobName))

                    $output = $child | Receive-Job -EV Err -EA SilentlyContinue

                    if ($returnOutput) {
                        $returnList += $output
                        $output | foreach-Object { 
                            $type = $_.gettype().name
                            if ($type -eq "String" -or $type -like "Int*") {
                                [System.Console]::WriteLine($_) 
                            }
                        }
                    }
                    else {
                        $output
                    }
                    if ($child.State -eq "Failed"){
                        $Global:pipelineFailed = $true
                        [System.Console]::WriteLine("`e[31mFAILED")
                        $($child.jobstateinfo.reason.message) | foreach-Object {
                            [System.Console]::WriteLine("`e[31m$_`e[0m")
                        }
                    }
                    [System.Console]::WriteLine(($jobAppendix -replace "<StateColor>", $stateColor) -replace "<JobName>",$jobName)
                }
            }
        } 
    }
    # Writing summary of which jobs failed, completed etc.
    if($finallyListJobs){
        Write-Output "`nListing job states:"
        $jobList.ChildJobs | ForEach-Object {
            $child = $_
            if ($child.State -ne "Completed" ) {
                Write-Output "::warning:: [-] State: $($child.State), Job: $($jobNames[$child.Id - $firstJobID])"
            }
            else {
                Write-Output "`e[32;1m [+] State: $($child.State)`e[0m, Job:`e[1m $($jobNames[$child.Id - $firstJobID])`e[0m"
            }
        }
    }
    return $returnList
}