AzureADPIM.psm1

$script:URLs = @{
    LoginURL = "https://login.microsoft.com"
    Resource = "https://graph.microsoft.com"
    PrivilegedRoles = "https://graph.microsoft.com/beta/privilegedRoles"
    PrivilegedRoleAssignments = "https://graph.microsoft.com/beta/privilegedRoleAssignments"
    PrivilegedRoleAssignmentRequests = "https://graph.microsoft.com/beta/privilegedRoleAssignmentRequests"
    PrivilegedOperationEvents = "https://graph.microsoft.com/beta/PrivilegedOperationEvents"
}

$script:OAuth = $null
$script:AzureADAdmin = $null

Function Connect-AzureADPIM
{
<#
.SYNOPSIS
    Authenticates to Azure AD and Microsoft Graph API app to get an OAuth token
 
.DESCRIPTION
    Authenticates to Azure AD and Microsoft Graph API app to get an OAuth token.
    The token must be stored in a variable.
    This token can be passed to other cmdlets in this module to provide authentication.
 
    Before using this you must set up an app registration in Azure AD with the following permissions:
    Microsoft Graph API:
        -Delegated:
            Directory.AccessAsUser.All
            PrivilegedAccess.ReadWrite.AzureAD
            PrivilegedAccess.ReadWrite.AzureResources
            User.Read
        -Application:
            Directory.Read.All
            Directory.ReadWrite.All
 
.EXAMPLE
    $OAuth = Connect-AzureADPIM -TenantDomain yourtenant.onmicrosoft.com -ClientId 27450a41-2843-4ba2-bc16-9df3dbe72cca -ClientSecret 'MR8Ur/J*N8fgEyIY'
 
.Parameter TenantDomain
    Specify a the Azure AD tenant domain: yourtenant.onmicrosoft.com
 
.Parameter ClientId
    Specify the client id for the Microsoft Graph API app you've created in Azure AD
 
.Parameter ClientSecret
    Specify the client secret for the Microsoft Graph API app you've created in Azure AD
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [ValidateScript({ $_ -like "*.onmicrosoft.com" })]
        [String]$TenantDomain,
        [Parameter(Mandatory)]
        [ValidateScript({try { [System.Guid]::Parse($_); $True } catch { $False }})]
        [String]$ClientId,
        [Parameter(Mandatory)]
        [String]$ClientSecret,
        [pscredential]$Credential
    )

    Begin
    {
        if (-Not (Get-Module -Name AzureADPreview -ListAvailable))
        {
            Install-Module -Name AzureADPreview
        }

        Import-Module -Name AzureADPreview -Global
    }

    Process
    {
        if (-Not $PSBoundParameters.ContainsKey('Credential'))
        {
            $Credential = Get-Credential -Message "Azure AD Administrator Credentials"
        }

        $Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.Password))

        # Connect to Azure AD
        $script:AzureADAdmin = Connect-AzureAD -Credential $Credential

        # Authenticate to Microsoft Graph API
        $Body = @{
            client_id = $ClientID
            client_secret = $ClientSecret
            username = $Credential.UserName
            password = $Password
            grant_type = 'password'
            resource = $URLs.Resource
            scope = 'openid'
        }
        $Result = Invoke-RestMethod -Method Post -Uri "$($URLs.LoginURL)/$TenantDomain/oauth2/token" -Body $Body
        $script:OAuth = $Result
        Write-Output -InputObject $Result
    }

    End
    {
    }
}

Function Get-AzureADPIMRole
{
<#
.SYNOPSIS
    Get a list of all Azure AD Roles managed by Azure AD Privileged Identity Management
 
.DESCRIPTION
    Get a list of all Azure AD Roles managed by Azure AD Privileged Identity Management
 
.EXAMPLE
    $OAuth | Get-AzureADPIMRole
 
.EXAMPLE
    Get-AzureADPIMRole -OAuth $OAuth
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding()]
    Param
    (
        [ValidateScript({try { [System.Guid]::Parse($_); $True } catch { $False }})]
        [String]$Id,
        [SupportsWildcards()]
        [String]$Name = "*",
        [ValidateScript({ $null -ne $_.access_token })]
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        if ($PSBoundParameters.ContainsKey("Id"))
        {
            # Fetch data about all privileged roles
            $Result = (Invoke-WebRequest -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedRoles)/$Id").Content | ConvertFrom-Json
        }
        else
        {
            # Fetch data about all privileged roles
            $Result = ((Invoke-WebRequest -UseBasicParsing -Headers $HeaderParams -Uri $URLs.PrivilegedRoles).Content | ConvertFrom-Json).Value
        }

        Write-Output -InputObject ($Result | Where-Object Name -like $Name)
    }

    End
    {
    }
}

Function Get-AzureADPIMRoleSetting
{
<#
.SYNOPSIS
    Get settings for one or more Azure AD PIM Roles
 
.DESCRIPTION
    Get settings for one or more Azure AD PIM Roles
 
.EXAMPLE
    Get-AzureADPIMRoleSetting -Id fe930be7-5e62-47db-91af-98c3a49a38b1 -OAuth $OAuth
 
.EXAMPLE
    Get-AzureADPIMRole -Name 'Global Administrator' | Get-AzureADPimRoleSetting
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding(DefaultParameterSetName = "byId")]
    Param
    (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, ParameterSetName = "byId")]
        [ValidateScript({try { [System.Guid]::Parse($_); $True } catch { $False }})]
        [String]$Id,
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = "byName")]
        [String]$Name,
        [ValidateScript({ $null -ne $_.access_token })]
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        if ($PSBoundParameters.ContainsKey("Name"))
        {
            $Id = Get-AzureADPIMRole -OAuth $OAuth -Name $Name | Select-Object -ExpandProperty Id
        }

        $Result = (Invoke-WebRequest -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedRoles)/$Id/Settings").Content | ConvertFrom-Json

        Write-Output -InputObject $Result
    }

    End
    {
    }
}

Function Get-AzureADPIMRoleSummary
{
<#
.SYNOPSIS
    Get summary information for one or more Azure AD PIM Roles
 
.DESCRIPTION
    Get summary information for one or more Azure AD PIM Roles
 
.EXAMPLE
    Get-AzureADPIMRoleSummary -Id fe930be7-5e62-47db-91af-98c3a49a38b1
 
.EXAMPLE
    Get-AzureADPIMRoles | Get-AzureADPIMRoleSummary
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding(DefaultParameterSetName = "byId")]
    Param
    (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, ParameterSetName = "byId")]
        [ValidateScript({try { [System.Guid]::Parse($_); $True } catch { $False }})]
        [String]$Id,
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = "byName")]
        [String]$Name,
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        if ($PSBoundParameters.ContainsKey("Name"))
        {
            $Id = Get-AzureADPIMRole -OAuth $OAuth -Name $Name | Select-Object -ExpandProperty Id
        }

        $Result = (Invoke-WebRequest -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedRoles)/$Id/summary").Content | ConvertFrom-Json

        Write-Output -InputObject $Result
    }

    End
    {
    }
}

Function Get-AzureADPIMAssignment
{
<#
.SYNOPSIS
    Get assignments for one or more Azure AD PIM Roles or admins
 
.DESCRIPTION
    Get assignments for one or more Azure AD PIM Roles or admins
 
.EXAMPLE
    Get-AzureADPIMAssignment
 
.EXAMPLE
    Get-AzureADPIMAssignment -RoleName 'Global Administrator'
 
.EXAMPLE
    Get-AzureADPIMAssignment -UserPrincipalName *admin_martin*
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding()]
    Param
    (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateScript({try { [System.Guid]::Parse($_); $True } catch { $False }})]
        [String]$RoleId,
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]$RoleName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [SupportsWildcards()]
        [String]$UserPrincipalName = "*",
        [SupportsWildcards()]
        [String]$AssignmentId = "*",
        [Switch]$My,
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        if ($PSBoundParameters.ContainsKey("RoleName"))
        {
            $RoleId = Get-AzureADPIMRole -OAuth $OAuth -Name $RoleName | Select-Object -ExpandProperty Id
        }

        if ($PSBoundParameters.ContainsKey("RoleName") -or $PSBoundParameters.ContainsKey("RoleId"))
        {
            $Result = (Invoke-WebRequest -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedRoles)/$RoleId/assignments").Content | ConvertFrom-Json
        }
        else
        {
            $Result = (Invoke-WebRequest -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedRoleAssignments)").Content | ConvertFrom-Json
        }

        if ($My.IsPresent)
        {
            $UserPrincipalName = $script:AzureADAdmin.Account.Id
        }

        $Roles = Get-AzureADPIMRole -OAuth $OAuth

        $Result.Value | ForEach-Object `
        {
            $User = Get-AzureADUser -ObjectId $_.userId
            $Role = $Roles | Where-Object Id -eq $_.roleId
            New-Object -TypeName psobject -Property @{
                AssignmentId = $_.Id
                UserId = $_.userId
                RoleId = $Role.Id
                User = $User.UserPrincipalName
                Role = $Role.Name
                IsElevated = $_.isElevated
                ExpirationDateTime = $_.expirationDateTime
                ResultMessage = $_.resultMessage
            }
        } | Where-Object { $_.User -like $UserPrincipalName -and $_.AssignmentId -like $AssignmentId }
    }

    End
    {
    }
}

Function New-AzureADPIMRoleAssignment
{
<#
.SYNOPSIS
    Create an Azure AD PIM Role assignment for one administrator
 
.DESCRIPTION
    Create an Azure AD PIM Role assignment for one administrator
 
.EXAMPLE
    New-AzureADPIMRoleAssignment -RoleId 729827e3-9c14-49f7-bb1b-9608f156bbb8 -UserPrincipalName admin_traale@preprodbanenor.onmicrosoft.com
 
.EXAMPLE
    New-AzureADPIMRoleAssignment -RoleName 'HelpDesk Administrator' -UserPrincipalName admin_traale@preprodbanenor.onmicrosoft.com
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory, ParameterSetName = 'byId')]
        [ValidateScript({try { [System.Guid]::Parse($_); $True } catch { $False }})]
        [String]$RoleId,
        [Parameter(Mandatory, ParameterSetName = 'byName')]
        [String]$RoleName,
        [Parameter(Mandatory, ParameterSetName = 'byId')]
        [Parameter(Mandatory, ParameterSetName = 'byName')]
        [String]$UserPrincipalName,
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        if ($PSBoundParameters.ContainsKey("RoleName"))
        {
            $RoleId = Get-AzureADPIMRole -OAuth $OAuth -Name $RoleName | Select-Object -ExpandProperty Id
        }

        $UserId = Get-AzureADUser -SearchString $UserPrincipalName | Select-Object -ExpandProperty ObjectId
        $AssignRole = @{
            userId = $UserId
            roleId = $RoleId
        }

        $Result = (Invoke-WebRequest -Method Post -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedRoleAssignments)" -Body $AssignRole).Content | ConvertFrom-Json

        Write-Output $Result
    }

    End
    {
    }
}

Function Remove-AzureADPIMAssignment
{
<#
.SYNOPSIS
    Removes an Azure AD PIM Role assignment
 
.DESCRIPTION
    Removes an Azure AD PIM Role assignment
 
.EXAMPLE
    Remove-AzureADPIMAssignment -AssignmentId 5a0e1f49-52a9-44f1-bfc1-bbe532327e38_729827e3-9c14-49f7-bb1b-9608f156bbb8
 
.EXAMPLE
    Get-AzureADPIMAssignment -UserPrincipalName *admin_martin* | Remove-AzureADPIMAssignment
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact='High')]
    Param
    (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [String]$AssignmentId,
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        $Assignment = Get-AzureADPIMAssignment -AssignmentId $AssignmentId -OAuth $OAuth
        if ($PSCmdlet.ShouldProcess($Assignment.User, "Remove assignment on role $($Assignment.Role)"))
        {
            (Invoke-WebRequest -Method Delete -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedRoleAssignments)/$AssignmentId").Content
        }
    }

    End
    {
    }
}

Function Set-AzureADPIMAssignmentType
{
<#
.SYNOPSIS
    Set an Azure AD PIM Role assignment as eligible or permanent
 
.DESCRIPTION
    Set an Azure AD PIM Role assignment as eligible or permanent
 
.EXAMPLE
    Set-AzureADPIMAssignmentType -AssignmentId 5a0e1f49-52a9-44f1-bfc1-bbe532327e38_729827e3-9c14-49f7-bb1b-9608f156bbb8 -Type Permanent
 
.EXAMPLE
    Get-AzureADPIMAssignment -UserPrincipalName *admin_martin* -RoleName 'Reports Reader' | Set-AzureADPIMAssignmentType -Type Eligible
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact='High')]
    Param
    (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [String]$AssignmentId,
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateSet('Eligible', 'Permanent')]
        [String]$Type,
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        $Assignment = Get-AzureADPIMAssignment -AssignmentId $AssignmentId -OAuth $OAuth
        if ($PSCmdlet.ShouldProcess($Assignment.User, "Change assignment on role $($Assignment.Role) to $Type"))
        {
            $Result = (Invoke-WebRequest -Method Post -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedRoleAssignments)/$AssignmentId/make$Type").Content | ConvertFrom-Json
        }

        $Result
    }

    End
    {
    }
}

Function New-AzureADPIMAssignmentRequest
{
<#
.SYNOPSIS
    Create a new Azure AD PIM Role assignment request
 
.DESCRIPTION
    Create a new Azure AD PIM Role assignment request.
    Roles that require Multi-Factor Authentication is not going to work.
 
.EXAMPLE
    New-AzureADPIMAssignmentRequest -RoleName 'HelpDesk Administrator' -Reason 'Reset users password' -Duration 1
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding(DefaultParameterSetName = 'byId')]
    Param
    (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'byId')]
        [ValidateScript({try { [System.Guid]::Parse($_); $True } catch { $False }})]
        [String]$RoleId,
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'byName')]
        [String]$RoleName,
        [ValidateRange(1, 72)]
        [Int]$Duration = 3,
        [Parameter(Mandatory)]
        [ValidateLength(1, 500)]
        [String]$Reason,
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        if ($PSBoundParameters.ContainsKey("RoleName"))
        {
            $RoleId = Get-AzureADPIMRole -OAuth $OAuth -Name $RoleName | Select-Object -ExpandProperty Id
        }

        $Request = @{
            roleId = $RoleId
            type = 'UserAdd'
            duration = $Duration
            reason = $Reason
        }

        Try
        {
            $Result = (Invoke-WebRequest -Method Post -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.privilegedRoleAssignmentRequests)" -Body $Request).Content | ConvertFrom-Json
            Write-Output $Result
        }
        Catch
        {
            if ("$_" -like "*(403) Forbidden*")
            {
                Write-Error -Message "This role requires MFA, and is thus not supported by this module."
            }
        }
    }

    End
    {
    }
}

Function Get-AzureADPIMAssignmentRequest
{
<#
.SYNOPSIS
    Get Azure AD PIM Role assignment requests
 
.DESCRIPTION
    Get Azure AD PIM Role assignment requests
 
.EXAMPLE
    Get-AzureADPIMAssignmentRequest
 
.EXAMPLE
    Get-AzureADPIMAssignmentRequest -My
 
.EXAMPLE
    Get-AzureADPIMAssignmentRequest -RoleName 'HelpDesk Administrator'
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding()]
    Param
    (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateScript({try { [System.Guid]::Parse($_); $True } catch { $False }})]
        [String]$RoleId,
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]$RoleName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [SupportsWildcards()]
        [String]$UserPrincipalName = "*",
        [Switch]$My,
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        if ($PSBoundParameters.ContainsKey("RoleName"))
        {
            $RoleId = Get-AzureADPIMRole -OAuth $OAuth -Name $RoleName | Select-Object -ExpandProperty Id
        }

        $Result = ((Invoke-WebRequest -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedRoleAssignmentRequests)").Content | ConvertFrom-Json).Value

        if ($PSBoundParameters.ContainsKey("RoleName") -or $PSBoundParameters.ContainsKey("RoleId"))
        {
            $Result = $Result | Where-Object roleId -eq $RoleId
        }

        if ($My.IsPresent)
        {
            $UserPrincipalName = $script:AzureADAdmin.Account.Id
        }

        $Roles = Get-AzureADPIMRole -OAuth $OAuth

        $Result | ForEach-Object `
        {
            $User = Get-AzureADUser -ObjectId $_.userId
            $Role = $Roles | Where-Object Id -eq $_.roleId
            New-Object -TypeName psobject -Property @{
                RequestId = $_.Id
                UserId = $_.userId
                RoleId = $Role.Id
                User = $User.UserPrincipalName
                Role = $Role.Name
                AssignmentState = $_.assignmentState
                RequestedDateTime = $_.requestedDateTime
                Status = $_.status
                Duration = $_.duration
                Reason = $_.Reason
                TicketNumber = $_.ticketNumber
                TicketSystem = $_.ticketSystem
                Schedule = $_.schedule
            }
        } | Where-Object { $_.User -like $UserPrincipalName }
    }

    End
    {
    }
}

Function Get-AzureADPIMAuditEvent
{
<#
.SYNOPSIS
    Get Azure AD PIM audit events
 
.DESCRIPTION
    Get Azure AD PIM audit events
 
.EXAMPLE
    Get-AzureADPIMAuditEvent
 
.NOTES
    Author: Martin Norlunn
    Updated: 08.09.2019
#>

    [CmdletBinding()]
    Param
    (
        [PSCustomObject]$OAuth
    )

    Begin
    {
        if (-Not $PSBoundParameters.ContainsKey('OAuth'))
        {
            $OAuth = $script:OAuth
        }

        $HeaderParams = @{
            Authorization = "$($OAuth.token_type) $($OAuth.access_token)"
        }
    }

    Process
    {
        $Result = (Invoke-WebRequest -UseBasicParsing -Headers $HeaderParams -Uri "$($URLs.PrivilegedOperationEvents)").Content | ConvertFrom-Json
        Write-Output -InputObject $Result.Value
    }

    End
    {
    }
}