<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Powershell Archives - the Sysadmin Channel</title>
	<atom:link href="https://thesysadminchannel.com/powershell/feed/" rel="self" type="application/rss+xml" />
	<link>https://thesysadminchannel.com/powershell/</link>
	<description>Documenting My Life as a System Administrator</description>
	<lastBuildDate>Fri, 01 Mar 2024 21:40:09 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
<site xmlns="com-wordpress:feed-additions:1">144174110</site>	<item>
		<title>Get Entra ID PIM Role Assignment Using Graph API</title>
		<link>https://thesysadminchannel.com/get-entra-id-pim-role-assignment-using-graph-api/</link>
					<comments>https://thesysadminchannel.com/get-entra-id-pim-role-assignment-using-graph-api/#comments</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Sun, 18 Feb 2024 00:32:39 +0000</pubDate>
				<category><![CDATA[Azure]]></category>
		<category><![CDATA[Graph API]]></category>
		<category><![CDATA[active or eligible azure ad role]]></category>
		<category><![CDATA[azure ad role audit]]></category>
		<category><![CDATA[pim role assignment graph api]]></category>
		<category><![CDATA[Use PowerShell to get Entra ID PIM Role assignment]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=5023</guid>

					<description><![CDATA[<p>In a previous post I wrote a script to be able to get Entra ID Role assignments using the older Azure AD PowerShell module. However, with the addition of Graph API and seeing how that&#8217;s the way of the future,&#8230; <a href="https://thesysadminchannel.com/get-entra-id-pim-role-assignment-using-graph-api/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/get-entra-id-pim-role-assignment-using-graph-api/">Get Entra ID PIM Role Assignment Using Graph API</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>In a <a href="https://thesysadminchannel.com/get-pim-role-assignment-status-for-azure-ad-using-powershell/" rel="noopener" target="_blank">previous post</a> I wrote a script to be able to get Entra ID Role assignments using the older Azure AD PowerShell module. However, with the addition of Graph API and seeing how that&#8217;s the way of the future, I wanted to share my updated script to use Graph API instead.  This will still require the Graph API PowerShell module since it uses some PowerShell cmdlets instead of the native REST calls, but it&#8217;s great to use and outputs the information we require.<br />
&nbsp;</p>
<div id="tableofcontents">
<h2>Table Of Contents</h2>
<ul>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#pimrole">Get Entra ID PIM Role Assignment Using Graph API</a></li>
<ul>
<li><a href="#script">PowerShell Script</a></li>
<li><a href="#parameters">Script Parameters</a></li>
<li><a href="#examples">Examples and Usage</a></li>
</ul>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<p>&nbsp;</p>
<div id="requirements" style="scroll-margin-top: 10px;"></div>
<h2>Requirements</h2>
<p>In this article I am going to be sharing a script to get the Entra ID PIM Role eligibility and active assignments but there are a few things we will need in order to run the script successfully.  Let us list out what&#8217;s needed now.</p>
<ul>
<li><a href="https://www.powershellgallery.com/packages/Microsoft.Graph/" rel="noopener" target="_blank">Graph PowerShell SDK v1.0</a> and beta module</li>
<li>Entra ID P2 License</li>
<li>Graph API Scopes:</li>
<ul>
<li>Directory.Read.All</li>
<p>	       &#8211;OR&#8211;</p>
<li>RoleEligibilitySchedule.Read.Directory</li>
<li>RoleAssignmentSchedule.Read.Directory</li>
<li>RoleManagement.Read.Directory</li>
</ul>
</ul>
<p>&nbsp;</p>
<div id="pimrole" style="scroll-margin-top: 10px;"></div>
<h2>Get Entra ID PIM Role Assignment Using Graph API</h2>
<p>As mentioned above, we will need at least 1 Entra ID P2 license since that is what allows us to use PIM in our tenant.  We should also confirm we have the Graph PowerShell SDK v1.0 and beta modules.<br />
&nbsp;</p>
<p>Finally, I like to use PowerShell 7+ since that is better optimized for PowerShell as opposed to the default Windows PowerShell that comes pre-installed with Windows.  This is not a requirement but more of a personal preference.<br />
&nbsp;</p>
<div id="script" style="scroll-margin-top: 10px;"></div>
<h2>PowerShell Script</h2>
<p>Now let&#8217;s get to the reason why you checked this article.  Below is the PowerShell script to get PIM Role assignment using Graph API.</p>
<pre class="brush: powershell; title: ; notranslate">
Function Get-MgPimRoleAssignment {
&lt;#
.SYNOPSIS
    This will check if a user is added to PIM or standing access.

.LINK
    https://thesysadminchannel.com/get-entra-id-pim-role-assignment-using-graph-api -

.NOTES
    Name: Get-MgPimRoleAssignment
    Author: Paul Contreras
    Version: 2.4
    DateCreated: 2023-Jun-15
#&gt;

    [CmdletBinding()]
    param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'User',
            Position  = 0
        )]
        [Alias('UserPrincipalName')]
        [string[]]  $UserId,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Role',
            Position  = 1
        )]
        [Alias('DisplayName')]
        [ValidateSet(
            'Application administrator',
            'Application developer',
            'Attack payload author',
            'Attack simulation administrator',
            'Attribute assignment administrator',
            'Attribute assignment reader',
            'Attribute definition administrator',
            'Attribute definition reader',
            'Authentication administrator',
            'Authentication policy administrator',
            'Azure AD joined device local administrator',
            'Azure DevOps administrator',
            'Azure Information Protection administrator',
            'B2C IEF Keyset administrator',
            'B2C IEF Policy administrator',
            'Billing administrator',
            'Cloud App Security Administrator',
            'Cloud application administrator',
            'Cloud device administrator',
            'Compliance administrator',
            'Compliance data administrator',
            'Conditional Access administrator',
            'Customer LockBox access approver',
            'Desktop Analytics administrator',
            'Directory readers',
            'Directory writers',
            'Domain name administrator',
            'Dynamics 365 administrator',
            'Edge administrator',
            'Exchange administrator',
            'Exchange recipient administrator',
            'External ID user flow administrator',
            'External ID user flow attribute administrator',
            'External Identity Provider administrator',
            'Global administrator',
            'Global reader',
            'Groups administrator',
            'Guest inviter',
            'Helpdesk administrator',
            'Hybrid identity administrator',
            'Identity Governance Administrator',
            'Insights administrator',
            'Insights Analyst',
            'Insights business leader',
            'Intune administrator',
            'Kaizala administrator',
            'Knowledge administrator',
            'Knowledge manager',
            'License administrator',
            'Lifecycle Workflows Administrator',
            'Message center privacy reader',
            'Message center reader',
            'Network administrator',
            'Office apps administrator',
            'Password administrator',
            'Permissions Management Administrator',
            'Power BI administrator',
            'Power platform administrator',
            'Printer administrator',
            'Printer technician',
            'Privileged authentication administrator',
            'Privileged role administrator',
            'Reports reader',
            'Search administrator',
            'Search editor',
            'Security administrator',
            'Security operator',
            'Security reader',
            'Service support administrator',
            'SharePoint administrator',
            'Skype for Business administrator',
            'Teams administrator',
            'Teams communications administrator',
            'Teams Communications Support Engineer',
            'Teams Communications Support Specialist',
            'Teams devices administrator',
            'Tenant Creator',
            'Usage summary reports reader',
            'User administrator',
            'Virtual Visits Administrator',
            'Windows 365 Administrator',
            'Windows update deployment administrator',
            'Yammer Administrator'
        )]
        [string]    $RoleName,


        [Parameter(
            Mandatory = $false
        )]
        [ValidateSet(
            'Eligibile',
            'Active'
        )]
        [string]    $PimAssignment,


        [Parameter(
            Mandatory = $false
        )]
        [string]    $TenantId,


        [Parameter(
            Mandatory = $false
        )]
        [switch]    $HideActivatedRoles
    )

    BEGIN {
        $ConnectionGraph = Get-MgContext
        $ConnectionGraph.Scopes = $ConnectionGraph.Scopes -replace &quot;write&quot;,&quot;&quot; | select -Unique
        'RoleEligibilitySchedule.Read.Directory', 'RoleAssignmentSchedule.Read.Directory', 'RoleManagement.Read.Directory' | ForEach-Object {
            if ($ConnectionGraph.Scopes -notcontains $_) {
                Connect-Graph -Scopes RoleEligibilitySchedule.Read.Directory, RoleAssignmentSchedule.Read.Directory, RoleManagement.Read.Directory -ErrorAction Stop
                continue
            }
        }

        if (-not ($PSBoundParameters.ContainsKey('TenantId'))) {
            $TenantId = $ConnectionGraph.TenantId
        }
    }

    PROCESS {
        $RoleDefinitions = Invoke-GraphRequest -Uri 'https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions' | select -ExpandProperty value

        $RoleHash   = @{}
        $RoleDefinitions | select id, displayname | ForEach-Object {$RoleHash.Add($_.DisplayName, $_.Id) | Out-Null}
        $RoleDefinitions | select id, displayname | ForEach-Object {$RoleHash.Add($_.Id, $_.DisplayName) | Out-Null}

        if ($PSBoundParameters.ContainsKey('UserId')) {
            foreach ($User in $UserId) {
                try {
                    [System.Collections.Generic.List[Object]]$RoleMemberList = @()
                    $PropertyList = 'DisplayName', 'UserPrincipalName', 'Id', 'AccountEnabled'
                    $AzUser = Get-MgUser -UserId $User -Property $PropertyList | select $PropertyList

                    if ($PSBoundParameters.ContainsKey('PimAssignment')) { #if active or eligible is selected, no need to get other option
                        if ($PSBoundParameters.ContainsValue('Active')) {
                            $AssignmentList = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter &quot;PrincipalId eq '$($AzUser.id)'&quot; -ExpandProperty Principal,DirectoryScope -All
                            $AssignmentList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value &quot;Active&quot; -Force -PassThru | Out-Null
                            $AssignmentList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties.&quot;@odata.type&quot;.split('.')[2] } -Force -PassThru | Out-Null
                            $AssignmentList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null}
                        }

                        if ($PSBoundParameters.ContainsValue('Eligibile')) {
                            $EligibleList = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter &quot;PrincipalId eq '$($AzUser.id)'&quot; -ExpandProperty Principal,DirectoryScope -All
                            $EligibleList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value &quot;Eligibile&quot; -Force -PassThru | Out-Null
                            $EligibleList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties.&quot;@odata.type&quot;.split('.')[2] } -Force -PassThru | Out-Null
                            $EligibleList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null}
                        }
                    } else {
                        $AssignmentList = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter &quot;PrincipalId eq '$($AzUser.id)'&quot; -ExpandProperty Principal,DirectoryScope -All
                        $AssignmentList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value &quot;Active&quot; -Force -PassThru | Out-Null
                        $AssignmentList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties.&quot;@odata.type&quot;.split('.')[2] } -Force -PassThru | Out-Null

                        $EligibleList = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter &quot;PrincipalId eq '$($AzUser.id)'&quot; -ExpandProperty Principal,DirectoryScope -All
                        $EligibleList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value &quot;Eligibile&quot; -Force -PassThru | Out-Null
                        $EligibleList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties.&quot;@odata.type&quot;.split('.')[2] } -Force -PassThru | Out-Null

                        $AssignmentList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null}
                        $EligibleList   | ForEach-Object {$RoleMemberList.Add($_) | Out-Null}
                    }

                    if ($RoleMemberList) {
                        $Output = foreach ($RoleMember in $RoleMemberList) {
                            if ($RoleMember.DirectoryScopeId -eq '/') {
                                $DirectoryScope = 'Global'
                            }
                            elseif ($RoleMember.DirectoryScopeId -match 'administrativeUnits') {
                                $DirectoryScope = $RoleMember.DirectoryScope.AdditionalProperties.displayName
                            }
                            else {
                                $DirectoryScope = 'Unknown'
                            }

                            if ($RoleMember.ScheduleInfo.Expiration.Type -eq 'noExpiration') {
                                $DurationInMonths = 'Permanent'
                                $EndDate = 'Permanent'
                            } else {
                                $Days = ($RoleMember.ScheduleInfo.Expiration.EndDateTime) - ($RoleMember.ScheduleInfo.StartDateTime) | select -ExpandProperty TotalDays
                                $DurationInMonths = $Days / 30.4167 -as [int]
                                $EndDate = (Get-Date $RoleMember.ScheduleInfo.Expiration.EndDateTime).ToLocalTime()
                            }

                            if ($RoleMember.AssignmentScope -eq 'Active' -and $RoleMember.AssignmentType -eq 'Activated') {
                                $AssignmentScope = 'PimActivated'
                            } else {
                                $AssignmentScope = $RoleMember.AssignmentScope
                            }

                            if ($RoleMember.ScheduleInfo.StartDateTime -and $RoleMember.CreatedDateTime) {
                                $StartDateTime = (Get-Date $RoleMember.ScheduleInfo.StartDateTime).ToLocalTime()
                            } else {
                                $StartDateTime = (Get-Date 1/1/1999 -Hour 0 -Minute 0 -Millisecond 0)
                            }

                            [PSCustomObject]@{
                                UserPrincipalName   = $AzUser.UserPrincipalName
                                AzureADRole         = $RoleHash[$RoleMember.RoleDefinitionId]
                                PimAssignment       = $AssignmentScope
                                EndDateTime         = $EndDate
                                AccountEnabled      = $AzUser.AccountEnabled
                                DirectoryScope      = $DirectoryScope
                                DurationInMonths    = $DurationInMonths
                                MemberType          = $RoleMember.MemberType
                                AccountType         = $RoleMember.AccountType
                                StartDateTime       = $StartDateTime
                            }
                        }

                        if ($PSBoundParameters.ContainsKey('HideActivatedRoles')) {
                            $Output | Sort-Object PimAssignment, AzureADRole | Where-Object {$_.PimAssignment -ne 'PimActivated'}
                        } else {
                            $Output | Sort-Object PimAssignment, AzureADRole
                        }
                    }

                } catch {
                    Write-Error $_.Exception.Message
                }
            }
        } #end userid parameter set

        if ($PSBoundParameters.ContainsKey('RoleName')) {
            try {
                [System.Collections.Generic.List[Object]]$RoleMemberList = @()

                if ($PSBoundParameters.ContainsKey('PimAssignment')) {
                    if ($PSBoundParameters.ContainsValue('Active')) {
                        $AssignmentList = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter &quot;RoleDefinitionId eq '$($RoleHash[$RoleName])'&quot; -ExpandProperty Principal,DirectoryScope -All
                        $AssignmentList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value &quot;Active&quot; -Force -PassThru | Out-Null
                        $AssignmentList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties.&quot;@odata.type&quot;.split('.')[2] } -Force -PassThru | Out-Null
                        $AssignmentList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null}
                    }

                    if ($PSBoundParameters.ContainsValue('Eligibile')) {
                        $EligibleList = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter &quot;RoleDefinitionId eq '$($RoleHash[$RoleName])'&quot; -ExpandProperty Principal,DirectoryScope -All
                        $EligibleList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value &quot;Eligibile&quot; -Force -PassThru | Out-Null
                        $EligibleList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties.&quot;@odata.type&quot;.split('.')[2] } -Force -PassThru | Out-Null
                        $EligibleList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null}
                    }
                  } else {
                    $AssignmentList = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter &quot;RoleDefinitionId eq '$($RoleHash[$RoleName])'&quot; -ExpandProperty Principal,DirectoryScope -All
                    $AssignmentList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value &quot;Active&quot; -Force -PassThru | Out-Null
                    $AssignmentList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties.&quot;@odata.type&quot;.split('.')[2] } -Force -PassThru | Out-Null

                    $EligibleList = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter &quot;RoleDefinitionId eq '$($RoleHash[$RoleName])'&quot; -ExpandProperty Principal,DirectoryScope -All
                    $EligibleList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value &quot;Eligibile&quot; -Force -PassThru | Out-Null
                    $EligibleList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties.&quot;@odata.type&quot;.split('.')[2] } -Force -PassThru | Out-Null

                    $AssignmentList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null}
                    $EligibleList   | ForEach-Object {$RoleMemberList.Add($_) | Out-Null}
                }

                if ($RoleMemberList) {
                    $Output = foreach ($RoleMember in $RoleMemberList) {
                        if ($RoleMember.DirectoryScopeId -eq '/') {
                            $DirectoryScope = 'Global'
                        }
                        elseif ($RoleMember.DirectoryScopeId -match 'administrativeUnits') {
                            $DirectoryScope = $RoleMember.DirectoryScope.AdditionalProperties.displayName
                        }
                        else {
                            $DirectoryScope = 'Unknown'
                        }

                        if ($RoleMember.ScheduleInfo.Expiration.Type -eq 'noExpiration') {
                            $DurationInMonths = 'Permanent'
                            $EndDate = 'Permanent'
                        } else {
                            $Days = ($RoleMember.ScheduleInfo.Expiration.EndDateTime) - ($RoleMember.ScheduleInfo.StartDateTime) | select -ExpandProperty TotalDays
                            $DurationInMonths = $Days / 30.4167 -as [int]
                            $EndDate = (Get-Date $RoleMember.ScheduleInfo.Expiration.EndDateTime)#.ToString('yyyy-MM-dd')
                        }

                        if ($RoleMember.AssignmentScope -eq 'Active' -and $RoleMember.AssignmentType -eq 'Activated') {
                            $AssignmentScope = 'PimActivated'
                        } else {
                            $AssignmentScope = $RoleMember.AssignmentScope
                        }

                        if ($RoleMember.ScheduleInfo.StartDateTime -and $RoleMember.CreatedDateTime) {
                            $StartDateTime = (Get-Date $RoleMember.ScheduleInfo.StartDateTime).ToLocalTime()
                        } else {
                            $StartDateTime = (Get-Date 1/1/1999 -Hour 0 -Minute 0 -Millisecond 0)
                        }

                        switch ($RoleMember.AccountType) {

                            'User' {
                                [PSCustomObject]@{
                                    UserPrincipalName   = $RoleMember.Principal.AdditionalProperties.userPrincipalName
                                    AzureADRole         = $RoleHash[$RoleMember.RoleDefinitionId]
                                    PimAssignment       = $AssignmentScope
                                    EndDateTime         = $EndDate
                                    AccountEnabled      = $RoleMember.Principal.AdditionalProperties.accountEnabled
                                    DirectoryScope      = $DirectoryScope
                                    DurationInMonths    = $DurationInMonths
                                    MemberType          = $RoleMember.MemberType
                                    AccountType         = $RoleMember.AccountType
                                    StartDateTime       = $StartDateTime
                                }
                            }

                            'Group' {
                                $GroupMemberList = Get-MgGroupTransitiveMember -GroupId $RoleMember.PrincipalId
                                foreach ($GroupMember in $GroupMemberList) {
                                    [PSCustomObject]@{
                                        UserPrincipalName   = $GroupMember.AdditionalProperties.userPrincipalName
                                        AzureADRole         = $RoleHash[$RoleMember.RoleDefinitionId]
                                        PimAssignment       = $AssignmentScope
                                        EndDateTime         = $EndDate
                                        AccountEnabled      = $GroupMember.AdditionalProperties.accountEnabled
                                        DirectoryScope      = $DirectoryScope
                                        DurationInMonths    = $DurationInMonths
                                        MemberType          = $RoleMember.MemberType
                                        AccountType         = $GroupMember.AdditionalProperties.'@odata.type'.Split('.')[2]
                                        StartDateTime       = $StartDateTime
                                    }
                                }
                            }

                            'servicePrincipal' {
                                [PSCustomObject]@{
                                    UserPrincipalName   = $RoleMember.Principal.additionalproperties.displayName
                                    AzureADRole         = $RoleHash[$RoleMember.RoleDefinitionId]
                                    PimAssignment       = $AssignmentScope
                                    EndDateTime         = $EndDate
                                    AccountEnabled      = $RoleMember.Principal.AdditionalProperties.accountEnabled
                                    DirectoryScope      = $DirectoryScope
                                    DurationInMonths    = $DurationInMonths
                                    MemberType          = $RoleMember.MemberType
                                    AccountType         = $RoleMember.AccountType
                                    StartDateTime       = $StartDateTime
                                }
                            }
                        }
                    }

                    if ($PSBoundParameters.ContainsKey('HideActivatedRoles')) {
                        $Output | Sort-Object PimAssignment, AzureADRole | Where-Object {$_.PimAssignment -ne 'PimActivated'}
                    } else {
                        $Output | Sort-Object PimAssignment, AzureADRole
                    }
                }

            } catch {
                Write-Error $_.Exception.Message
            }
        } #end rolename parameter set
    }

    END {}

}
</pre>
<p>&nbsp;</p>
<div id="parameters" style="scroll-margin-top: 10px;"></div>
<h2>Script Parameters</h2>
<p>When using this script, you can use the following parameters to customize the output. Let&#8217;s go over those now.<br />
&nbsp;</p>
<h3>    -UserId</h3>
<p>DataType: string<br />
Description: Specify the UserId or UserPrincipalName of the principal you want to find active or eligible roles.<br />
&nbsp;</p>
<h3>    -RoleName</h3>
<p>DataType: string<br />
Description: Specify the Entra ID Role name to get all principals that are assigned that role.<br />
&nbsp;</p>
<h3>    -PimAssignment</h3>
<p>DataType: string<br />
Description: Specify either Active or Eligible to display those results..<br />
&nbsp;</p>
<h3>    -TenantId</h3>
<p>DataType: string<br />
Description: Specify the tenant Id to query that specific tenant. You must be authenticated to that tenant.<br />
&nbsp;</p>
<h3>    -HideActivatedRoles</h3>
<p>DataType: switch<br />
Description: When used, the results will hide all PIM activated roles.<br />
&nbsp;</p>
<div id="examples" style="scroll-margin-top: 10px;"></div>
<h3>Example 1: Specifying the UserId parameter</h3>
<pre class="brush: powershell; title: ; notranslate">
Get-MgPimRoleAssignment -UserId homer@thesysadminchannel.com

UserPrincipalName : homer@thesysadminchannel.com
AzureADRole       : Helpdesk Administrator
PimAssignment     : Eligibile
EndDateTime       : Permanent
AccountEnabled    : True
DirectoryScope    : Admin Unit
DurationInMonths  : Permanent
MemberType        : Direct
AccountType       : user
StartDateTime     : 2/19/2024 3:34:32 PM
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2024/02/Pim-Role-assignment-with-userid-parameter.png"><img fetchpriority="high" decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2024/02/Pim-Role-assignment-with-userid-parameter.png" alt="Pim Role assignment with userid parameter" width="829" height="309" class="aligncenter size-full wp-image-5032" srcset="https://thesysadminchannel.com/wp-content/uploads/2024/02/Pim-Role-assignment-with-userid-parameter.png?v=1708401893 829w, https://thesysadminchannel.com/wp-content/uploads/2024/02/Pim-Role-assignment-with-userid-parameter-768x286.png?v=1708401893 768w" sizes="(max-width: 829px) 100vw, 829px" /></a><br />
&nbsp;</p>
<h3>Example 2: Specifying the RoleName parameter that are eligible</h3>
<pre class="brush: powershell; title: ; notranslate">
Get-MgPimRoleAssignment -RoleName 'Helpdesk administrator' -PimAssignment Eligibile

UserPrincipalName : luke@thesysadminchannel.com
AzureADRole       : Helpdesk Administrator
PimAssignment     : Eligibile
EndDateTime       : Permanent
AccountEnabled    : True
DirectoryScope    : Global
DurationInMonths  : Permanent
MemberType        : Direct
AccountType       : user
StartDateTime     : 2/19/2024 3:43:16 PM

UserPrincipalName : homer@thesysadminchannel.com
AzureADRole       : Helpdesk Administrator
PimAssignment     : Eligibile
EndDateTime       : Permanent
AccountEnabled    : True
DirectoryScope    : Admin Unit
DurationInMonths  : Permanent
MemberType        : Direct
AccountType       : user
StartDateTime     : 2/19/2024 3:34:32 PM
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2024/02/Pim-Role-assignment-with-rolename-parameter.png"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2024/02/Pim-Role-assignment-with-rolename-parameter.png" alt="Pim Role assignment with rolename parameter" width="991" height="587" class="aligncenter size-full wp-image-5033" srcset="https://thesysadminchannel.com/wp-content/uploads/2024/02/Pim-Role-assignment-with-rolename-parameter.png?v=1708402369 991w, https://thesysadminchannel.com/wp-content/uploads/2024/02/Pim-Role-assignment-with-rolename-parameter-125x75.png?v=1708402369 125w, https://thesysadminchannel.com/wp-content/uploads/2024/02/Pim-Role-assignment-with-rolename-parameter-768x455.png?v=1708402369 768w" sizes="(max-width: 991px) 100vw, 991px" /></a><br />
&nbsp;</p>
<div id="conclusion" style="scroll-margin-top: 10px;"></div>
<h2>Conclusion</h2>
<p>Hopefully this article was able to help you get Entra ID PIM Role Assignment Using Graph API.  With this script, you should be able to get all active, eligible AND eligible assignments that have been activated.<br />
&nbsp;</p>
<p>The post <a href="https://thesysadminchannel.com/get-entra-id-pim-role-assignment-using-graph-api/">Get Entra ID PIM Role Assignment Using Graph API</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/get-entra-id-pim-role-assignment-using-graph-api/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5023</post-id>	</item>
		<item>
		<title>Get Microsoft 365 License Usage Count Using PowerShell</title>
		<link>https://thesysadminchannel.com/get-microsoft-365-license-usage-count-using-powershell/</link>
					<comments>https://thesysadminchannel.com/get-microsoft-365-license-usage-count-using-powershell/#respond</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Sat, 11 Nov 2023 02:46:27 +0000</pubDate>
				<category><![CDATA[Azure]]></category>
		<category><![CDATA[Graph API]]></category>
		<category><![CDATA[Office365]]></category>
		<category><![CDATA[azure license count]]></category>
		<category><![CDATA[check my Office 365 license count]]></category>
		<category><![CDATA[get license count graph api]]></category>
		<category><![CDATA[Get Microsoft 365 License Usage Count Using PowerShell]]></category>
		<category><![CDATA[get-mguserlicensedetail]]></category>
		<category><![CDATA[How do I see all my Office Licenses]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=4920</guid>

					<description><![CDATA[<p>Keeping an eye on the available licenses in your Microsoft tenant is essential to ensuring you and your users have what is needed to keep the business running. Whether you assign licenses directly or you use Group Based Licensing, if&#8230; <a href="https://thesysadminchannel.com/get-microsoft-365-license-usage-count-using-powershell/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/get-microsoft-365-license-usage-count-using-powershell/">Get Microsoft 365 License Usage Count Using PowerShell</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Keeping an eye on the available licenses in your Microsoft tenant is essential to ensuring you and your users have what is needed to keep the business running. Whether you assign licenses directly or you use <a href="https://thesysadminchannel.com/assign-group-based-licensing-in-azure-ad/" rel="noopener" target="_blank">Group Based Licensing</a>, if a user needs a specific license, there shouldn&#8217;t be any hiccups when assigning.  Today I am going to share a PowerShell script to get Microsoft 365 license usage count using PowerShell and Graph API.</p>
<div id="tableofcontents">
<h2>Table Of Contents</h2>
<ul>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#powershell">Get Microsoft 365 License Usage Count Using PowerShell</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<div id="requirements" style="scroll-margin-top: 10px;"></div>
<h2>Requirements</h2>
<p>In order to query license information for your tenant, you will need the following API Scopes permitted.</p>
<ul>
<li>Microsoft.Graph or Microsoft.Graph.Beta PowerShell modules</li>
<li>Directory.Read.All or Organization.Read.All</li>
</ul>
<p>&nbsp;</p>
<div id="powershell" style="scroll-margin-top: 10px;"></div>
<h2>Get Microsoft 365 License Usage Count Using PowerShell</h2>
<p>Before we get into the PowerShell script, I wanted to point out that the Microsoft API&#8217;s don&#8217;t show the friendly display names for these licenses.  Instead, they use a SkuPartNumber to give you an idea of what the licenses is regarding.  The problem here is that you also won&#8217;t find the SkuPartNumber anywhere in the portal so it&#8217;s kind of a pain to make sure the license you&#8217;re targeting in the API is in fact the license in the Azure portal.<br />
&nbsp;</p>
<p>Luckily, there is a Microsoft Doc that has this information but it&#8217;s not always up to date.  The link to that doc is <a href="https://learn.microsoft.com/en-us/entra/identity/users/licensing-service-plan-reference" rel="noopener" target="_blank">https://learn.microsoft.com/en-us/entra/identity/users/licensing-service-plan-reference</a>.  There&#8217;s about 400+ Sku&#8217;s that are shown so it&#8217;s also nice that they have provided a csv file that we can use PowerShell to be able to pull these names into our Script.  </p>
<pre class="brush: powershell; title: ; notranslate">
$LicenseFile = 'C:\temp\m365license.csv'
$CutoffDate = (Get-Date).AddDays(-7)

if (Test-Path $LicenseFile) {
    $LastWriteTime = Get-ChildItem -Path $LicenseFile | select -ExpandProperty LastWriteTime

    if ($CutoffDate -gt $LastWriteTime) {
        #csv file is older than a week old.  Let us get a newer version
        Invoke-WebRequest -Uri 'https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv' -OutFile C:\temp\m365license.csv
    }
} else {
    #csv file was not found so let us download it now
    Invoke-WebRequest -Uri 'https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv' -OutFile C:\temp\m365license.csv
}

$csvList = Import-Csv C:\temp\m365license.csv
$LicenseHash = @{}

$csvList | ForEach-Object {
    if (-not $LicenseHash[$_.Guid]) {
        $LicenseHash.Add($_.GUID, $_.Product_Display_Name)
    }
}

$LicenseList = Get-MgSubscribedSku

#Uncomment if you only want to display licenses that are maxed out
foreach ($License in $LicenseList) {
    if ($License.PrepaidUnits.Enabled -ge 1) {
        #if ($License.ConsumedUnits -ge $License.PrepaidUnits.Enabled) {
            [PSCustomObject]@{
                LicenseName   = $LicenseHash[$License.SkuId]
                SkuPartNumber = $License.SkuPartNumber
                SkuId         = $License.SkuId
                Remaining     = $License.PrepaidUnits.Enabled - $License.ConsumedUnits
                Enabled       = $License.PrepaidUnits.Enabled
                Used          = $License.ConsumedUnits
            }
        #}
    }
}
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/11/License-Count-Usage-PowerShell-Graph-API.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/11/License-Count-Usage-PowerShell-Graph-API.png" alt="Microsoft 365 License Usage Count PowerShell Graph API" width="1019" height="416" class="aligncenter size-full wp-image-4929" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/11/License-Count-Usage-PowerShell-Graph-API.png?v=1699669161 1019w, https://thesysadminchannel.com/wp-content/uploads/2023/11/License-Count-Usage-PowerShell-Graph-API-768x314.png?v=1699669161 768w" sizes="(max-width: 1019px) 100vw, 1019px" /></a><br />
&nbsp;</p>
<p>As you can see from above, the Identity Governance P2 Step Up license has not been updated in the downloadable csv file so the license name shows up blank.</p>
<div id="conclusion" style="scroll-margin-top: 10px;"></div>
<h2>Conclusion</h2>
<p>Hopefully this article was able to help you get Microsoft 365 License usage count using PowerShell and Graph API.  Sometimes we&#8217;re too busy to manually keep an eye on it so having this script along with an email alert would be helpful for preventing your licenses being maxed out.</p>
<p>The post <a href="https://thesysadminchannel.com/get-microsoft-365-license-usage-count-using-powershell/">Get Microsoft 365 License Usage Count Using PowerShell</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/get-microsoft-365-license-usage-count-using-powershell/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4920</post-id>	</item>
		<item>
		<title>Find Empty Groups in Active Directory using PowerShell</title>
		<link>https://thesysadminchannel.com/find-empty-groups-in-active-directory-using-powershell/</link>
					<comments>https://thesysadminchannel.com/find-empty-groups-in-active-directory-using-powershell/#comments</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Tue, 03 Oct 2023 01:30:46 +0000</pubDate>
				<category><![CDATA[Active Directory]]></category>
		<category><![CDATA[Powershell]]></category>
		<category><![CDATA[Find Empty Groups]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=4912</guid>

					<description><![CDATA[<p>Whether it&#8217;s time for spring cleaning or you&#8217;re just doing some general cleanup, it&#8217;s important to maintain a proper lifecycle around Active Directory groups. Many organizations love creating groups however, some (most?), don&#8217;t really like to do cleanup because they&#8217;re&#8230; <a href="https://thesysadminchannel.com/find-empty-groups-in-active-directory-using-powershell/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/find-empty-groups-in-active-directory-using-powershell/">Find Empty Groups in Active Directory using PowerShell</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Whether it&#8217;s time for spring cleaning or you&#8217;re just doing some general cleanup, it&#8217;s important to maintain a proper lifecycle around Active Directory groups. Many organizations love creating groups however, some (most?), don&#8217;t really like to do cleanup because they&#8217;re scared it might break something.  While this is true, it&#8217;s still a good thing to keep a tight ship and have some automation around cleanup.  Today we&#8217;re going to go over the query to find empty groups in Active Directory using PowerShell.<br />
&nbsp;</p>
<p>I previously wrote a post about using the ActiveDirectory module with Get-ADUser.  The idea was to <a href="https://thesysadminchannel.com/get-aduser-find-active-directory-users-using-powershell-ultimate-deep-dive/" rel="noopener" target="_blank">find AD users using PowerShell</a> and went over several advanced topics.  Feel free to check that to get familiar with the overall commands since Get-ADGroup is going to use something similar.<br />
&nbsp;</p>
<p>Here, the Get-ADGroup cmdlet is going to be used to filter all groups that have no members and move them to a separate OU for further processing.  Since we are a little cautious when it comes to making bulk changes like this, I would suggest moving them to a staging OU where they can be left there for 30-60 days.  Since these groups are empty, chances are no one is going to be missing them but it&#8217;s a good idea to separate them first, then move forward with deleting.<br />
&nbsp;</p>
<p>Before we delete anything, I would strongly recommend you <a href="https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-ad-recycle-bin-understanding-implementing-best-practices-and/ba-p/396944" rel="noopener" target="_blank">enable the AD recycle bin</a> so you can recover objects without hesitation.<br />
&nbsp;</p>
<h2>Find Empty Groups in Active Directory using PowerShell</h2>
<pre class="brush: powershell; title: ; notranslate">
#Get All empty groups in the entire domain. Be careful with Exchange and other built-in groups.
$AllEmptyGroupList = Get-ADGroup -Filter {Members -notlike &quot;*&quot; } -Properties Members, WhenChanged, WhenCreated

#Get all empty groups that have not been touched in longer than 6 months. Be careful with Exchange and other built-in groups.
$CutOffDate = (Get-Date).AddMonths(-6)
$SixMonthEmptyGroupList = Get-ADGroup -Filter {Members -notlike &quot;*&quot; -and WhenChanged -lt $CutOffDate} -Properties Members, WhenChanged, WhenCreated

#Get all stale groups from a specific OU (Preferred)
$EmptyGroupList = Get-ADGroup -Filter {Members -notlike &quot;*&quot; -and WhenChanged -lt $CutOffDate} -Properties Members, WhenChanged, WhenCreated -SearchBase 'OU=My Groups,DC=contoso,DC=com'
</pre>
<p>&nbsp;</p>
<p>Hopefully, you were able to understand how to find empty groups in Active Directory using PowerShell to better manage your group lifecycle.  If a group is empty and hasn&#8217;t been modified in over 6 months, it&#8217;s a pretty good sign that it is no longer needed and can be purged.<br />
&nbsp;</p>
<p>Again, I would highly recommend you enable the recycle bin but with this you should be able to start off slowly and decommissioning in whatever approach you feel necessary.</p>
<p>The post <a href="https://thesysadminchannel.com/find-empty-groups-in-active-directory-using-powershell/">Find Empty Groups in Active Directory using PowerShell</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/find-empty-groups-in-active-directory-using-powershell/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4912</post-id>	</item>
		<item>
		<title>Get Synced OU Configuration in Azure AD Connect PowerShell</title>
		<link>https://thesysadminchannel.com/get-synced-ou-configuration-in-azure-ad-connect-powershell/</link>
					<comments>https://thesysadminchannel.com/get-synced-ou-configuration-in-azure-ad-connect-powershell/#respond</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Sun, 28 May 2023 19:27:56 +0000</pubDate>
				<category><![CDATA[Azure]]></category>
		<category><![CDATA[Powershell]]></category>
		<category><![CDATA[OU Configuration in Azure AD Connect]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=4805</guid>

					<description><![CDATA[<p>If you manage a hybrid environment, chances are you&#8217;ve had to manage Azure AD Connect. Getting the configuration settings via the GUI is pretty nice to get, but opening the wizard prevents you from syncing so sometimes it may not&#8230; <a href="https://thesysadminchannel.com/get-synced-ou-configuration-in-azure-ad-connect-powershell/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/get-synced-ou-configuration-in-azure-ad-connect-powershell/">Get Synced OU Configuration in Azure AD Connect PowerShell</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>If you manage a hybrid environment, chances are you&#8217;ve had to manage Azure AD Connect. Getting the configuration settings via the GUI is pretty nice to get, but opening the wizard prevents you from syncing so sometimes it may not be possible.  Today we&#8217;re going to show you a snippet so you can export the OU Configuration in Azure AD Connect in your environment.</p>
<div id="tableofcontents">
<h2>Table Of Contents</h2>
<ul>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#ouconfig">Get Synced OU Configuration in Azure AD Connect using PowerShell</a></li>
<li><a href="#attributeconfig">Get Synced Attributes using PowerShell</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<div id="requirements" style="scroll-margin-top: 15px;"></div>
<h2>Requirements</h2>
<p>In order for you to get all of the synced organizational units as well as the synced attributes, there are a few things you must have in place.  First things first, you need to have a supported version of Azure AD Connect installed.  Since AAD Connect should be in a tier 0 security configuration, you may need to run this locally on the machine itself.</p>
<div id="ouconfig" style="scroll-margin-top: 15px;"></div>
<h2>Get Synced OU Configuration in Azure AD Connect using PowerShell</h2>
<p>If you have an Active Directory OU structure that&#8217;s pretty dense it may be challenging to figure out what exactly is being synced and what is not.  My lab is pretty simple and straight forward but here are the organizational units that are synced and what&#8217;s shown from the GUI.</p>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/05/Get-Synced-OU-Configuration-in-Azure-AD-Connect-GUI.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/05/Get-Synced-OU-Configuration-in-Azure-AD-Connect-GUI.png" alt="Get Synced OU Configuration in Azure AD Connect GUI" width="880" height="620" class="aligncenter size-full wp-image-4811" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/05/Get-Synced-OU-Configuration-in-Azure-AD-Connect-GUI.png?v=1685299311 880w, https://thesysadminchannel.com/wp-content/uploads/2023/05/Get-Synced-OU-Configuration-in-Azure-AD-Connect-GUI-768x541.png?v=1685299311 768w" sizes="(max-width: 880px) 100vw, 880px" /></a></p>
<p>&nbsp;<br />
Let&#8217;s get this same information from PowerShell.  Remember you will need to be logged in to the active AAD Connect server since that&#8217;s what is actually being synced to Azure AD.</p>
<pre class="brush: powershell; title: ; notranslate">
#Get Connect information for your on-premises domain.
$SyncConnector = Get-ADSyncConnector | Where-Object {$_.Name -notmatch ' - aad'}

#Get OU inclusion list
$SyncConnector.Partitions.ConnectorPartitionScope.ContainerInclusionList

#Get OU exclusion list
$SyncConnector.Partitions.ConnectorPartitionScope.ContainerExclusionList
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/05/Get-Synced-OU-Configuration-in-Azure-AD-Connect-PowerShell.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/05/Get-Synced-OU-Configuration-in-Azure-AD-Connect-PowerShell.png" alt="Get Synced OU Configuration in Azure AD Connect PowerShell" width="850" height="356" class="aligncenter size-full wp-image-4813" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/05/Get-Synced-OU-Configuration-in-Azure-AD-Connect-PowerShell.png?v=1685299521 850w, https://thesysadminchannel.com/wp-content/uploads/2023/05/Get-Synced-OU-Configuration-in-Azure-AD-Connect-PowerShell-768x322.png?v=1685299521 768w" sizes="(max-width: 850px) 100vw, 850px" /></a><br />
&nbsp;</p>
<p>As you can see from the images above, the only OU I am syncing to Azure AD is the &#8220;Home&#8221; OU.  I do have a few sub organizational units that are excluded from sync and that&#8217;s also shown in the PowerShell image.  Hopefully this will paint a pretty picture when you need to see which OUs are synced.</p>
<div id="attributeconfig" style="scroll-margin-top: 15px;"></div>
<h2>Get Synced Attributes using PowerShell</h2>
<p>If you need to get what attributes are syncing, there&#8217;s a way to get that information using PowerShell as well.  Not all attributes will show with an Azure AD attribute, but this is a good start to see what&#8217;s there and what&#8217;s not.<br />
&nbsp;</p>
<p>It is important to note that attributes syncing from your on-premises Active Directory will not show up exactly the same in Azure AD.  Therefore, we will show the on-premises sync connector as well as the Azure AD sync connector.  This is key if you have custom mappings or have enabled directory extension attributes to use for custom claims.</p>
<pre class="brush: powershell; title: ; notranslate">
#Get attributes syncing from on-premises Active Directory
$SyncConnector = Get-ADSyncConnector | Where-Object {$_.Name -notmatch ' - aad'}
$SyncConnector.AttributeInclusionList


#Get attributes syncing to Azure Active Directory
$SyncConnector = Get-ADSyncConnector | Where-Object {$_.Name -match ' - aad'}
$SyncConnector.AttributeInclusionList
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/05/Attribtue-Configuration-in-Azure-AD-Connect-Onprem.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/05/Attribtue-Configuration-in-Azure-AD-Connect-Onprem.png" alt="Attribtue Configuration in Azure AD Connect Onprem" width="854" height="336" class="aligncenter size-full wp-image-4815" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/05/Attribtue-Configuration-in-Azure-AD-Connect-Onprem.png?v=1685301017 854w, https://thesysadminchannel.com/wp-content/uploads/2023/05/Attribtue-Configuration-in-Azure-AD-Connect-Onprem-768x302.png?v=1685301017 768w" sizes="(max-width: 854px) 100vw, 854px" /></a></p>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/05/Attribtue-Configuration-in-Azure-AD-Connect-Cloud.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/05/Attribtue-Configuration-in-Azure-AD-Connect-Cloud.png" alt="Attribtue Configuration in Azure AD Connect Cloud" width="850" height="336" class="aligncenter size-full wp-image-4816" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/05/Attribtue-Configuration-in-Azure-AD-Connect-Cloud.png?v=1685301027 850w, https://thesysadminchannel.com/wp-content/uploads/2023/05/Attribtue-Configuration-in-Azure-AD-Connect-Cloud-768x304.png?v=1685301027 768w" sizes="(max-width: 850px) 100vw, 850px" /></a></p>
<div id="conclusion" style="scroll-margin-top: 15px;"></div>
<h2>Conclusion</h2>
<p>Hopefully this article was able to show you how to get synced OU configuration in Azure AD Connect PowerShell as well as the attributes that are currently synced.  This is pretty handy because when you open up the Azure AD Connect application, sync is temporarily disabled until the wizard is closed.<br />
&nbsp;</p>
<p>Being able to gather this information using PowerShell helps solve that problem so you can run it at anytime.  If you would like to see more sysadmin content, be sure to check out our <a href="https://www.youtube.com/c/theSysadminChannel" rel="noopener" target="_blank">YouTube Channel</a></p>
<p>The post <a href="https://thesysadminchannel.com/get-synced-ou-configuration-in-azure-ad-connect-powershell/">Get Synced OU Configuration in Azure AD Connect PowerShell</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/get-synced-ou-configuration-in-azure-ad-connect-powershell/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4805</post-id>	</item>
		<item>
		<title>Get Application Certificate and Secret Expiration with Graph API</title>
		<link>https://thesysadminchannel.com/get-application-certificate-and-secret-expiration-with-graph-api-powershell/</link>
					<comments>https://thesysadminchannel.com/get-application-certificate-and-secret-expiration-with-graph-api-powershell/#comments</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Tue, 17 Jan 2023 01:51:27 +0000</pubDate>
				<category><![CDATA[Azure]]></category>
		<category><![CDATA[Graph API]]></category>
		<category><![CDATA[azure app cert expiration PowerShell]]></category>
		<category><![CDATA[get application cert expiration date graph api]]></category>
		<category><![CDATA[Get Application Certificate and Secret Expiration with Graph API]]></category>
		<category><![CDATA[get secret expiration date powershell]]></category>
		<category><![CDATA[get secrete expiration graph api]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=4676</guid>

					<description><![CDATA[<p>In the world of Azure cloud automation we always need to ensure that our accounts are able to properly authenticate. Accounts with username and password might have Active Directory alert you when your password expires, however, what can we use&#8230; <a href="https://thesysadminchannel.com/get-application-certificate-and-secret-expiration-with-graph-api-powershell/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/get-application-certificate-and-secret-expiration-with-graph-api-powershell/">Get Application Certificate and Secret Expiration with Graph API</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>In the world of Azure cloud automation we always need to ensure that our accounts are able to properly authenticate.  Accounts with username and password might have Active Directory alert you when your password expires, however, what can we use to ensure the secrets or certificates tied to an App registration aren&#8217;t nearing expiration ( or worse, already expired).  Today I am going to share a PowerShell script that shows you how to get application certificate and secret expiration with Graph API.</p>
<div id="tableofcontents">
<h2>Table Of Contents</h2>
<ul>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#azureportal">Check an App Registration for Expired Keys in Azure Portal</a></li>
<li><a href="#powershell">Get Application Certificate and Secret Expiration with Graph API</a></li>
<ul>
<li><a href="#powershellscript">PowerShell Script</a></li>
<li><a href="#scriptparameters">Script Parameters</a></li>
<li><a href="#examples">Examples and Usage</a></li>
</ul>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<div id="requirements" style="scroll-margin-top: 15px;"></div>
<h2>Requirements</h2>
<p>In order to run this, there are a few things that need to be in place to ensure we don&#8217;t run into any errors. Let&#8217;s touch on those item now.<br />
&nbsp;</p>
<ul>
<li>Directory.Read.All Permissions</li>
<li>Application.Read.All Permissions</li>
<li>Microsoft.Graph PowerShell SDK Module</li>
</ul>
<div id="azureportal" style="scroll-margin-top: 15px;"></div>
<h2>Check an App Registration for Expired Keys in Azure Portal</h2>
<p>Before we get into the PowerShell script, let&#8217;s take a look at how to check this manually so we know exactly what to expect when looking at the results of the script.<br />
&nbsp;</p>
<p>Within Azure AD:</p>
<ul>
<li>Navigate to <a href="https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/RegisteredApps" rel="noopener" target="_blank">App Registrations</a></li>
<li>Select an App that has a certificate or secret added</li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/01/Azure-portal-expired-keys.jpg" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/01/Azure-portal-expired-keys.jpg" alt="Azure portal expired keys" width="1108" height="352" class="aligncenter size-full wp-image-4684" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/01/Azure-portal-expired-keys.jpg?v=1673896287 1108w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Azure-portal-expired-keys-1024x325.jpg?v=1673896287 1024w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Azure-portal-expired-keys-768x244.jpg?v=1673896287 768w" sizes="(max-width: 1108px) 100vw, 1108px" /></a><br />
&nbsp;</p>
<ul>
<li>Go to Certificates &#038; secrets blade</li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificate-Expiration.jpg" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificate-Expiration.jpg" alt="Certificate Expiration" width="1059" height="294" class="aligncenter size-full wp-image-4685" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificate-Expiration.jpg?v=1673896397 1059w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificate-Expiration-1024x284.jpg?v=1673896397 1024w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificate-Expiration-768x213.jpg?v=1673896397 768w" sizes="(max-width: 1059px) 100vw, 1059px" /></a><br />
&nbsp;</p>
<p>Here we can see the status of the certificate for this specific application. Specifically, we&#8217;re interested in the expiration date, certificate thumbprint and Key ID (certificate ID).<br />
&nbsp;</p>
<p>While the portal does give you a visual of when keys are expiring, it still requires you to take time out of your day to manually check.  Let&#8217;s take a look at how we can accomplish the same thing automatically using the PowerShell script I wrote.</p>
<div id="powershell" style="scroll-margin-top: 15px;"></div>
<h2>Get Application Certificate and Secret Expiration with Graph API PowerShell</h2>
<p>Now that we&#8217;ve gone over the manual method, let&#8217;s use PowerShell and Graph API to our advantage and show the same information in an automated fashion.  Since we know how to <a href="https://thesysadminchannel.com/automate-powershell-scripts-with-task-scheduler/" rel="noopener" target="_blank">automate Powershell Scripts With Task Scheduler</a>, we can schedule this on a daily basis and let it alert you without any additional effort on your end.<br />
&nbsp;</p>
<div id="powershellscript" style="scroll-margin-top: 15px;"></div>
<p>Now for the PowerShell script:</p>
<pre class="brush: powershell; title: ; notranslate">
Function Get-MgApplicationCertificateAndSecretExpiration {
&lt;#
.SYNOPSIS
    This will display all Applications that have certificates or secrets expiring within a certain timeframe


.NOTES
    Name: Get-MgApplicationCertificateAndSecretExpiration
    Author: Paul Contreras
    Version: 1.3
    DateCreated: 2022-Feb-8

.LINK
    https://thesysadminchannel.com/get-application-certificate-and-secret-expiration-with-graph-api-powershell -

.EXAMPLE
    Get-MgApplicationCertificateAndSecretExpiration

.EXAMPLE
    Get-MgApplicationCertificateAndSecretExpiration -ShowExpiredKeys
#&gt;

    [CmdletBinding(DefaultParameterSetName='Default')]
    param(
        [Parameter(
            Mandatory = $false,
            ParameterSetName = 'CertOnly'
        )]
        [switch]    $ShowOnlyCertificates,

        [Parameter(
            Mandatory = $false,
            ParameterSetName = 'SecretOnly'
        )]
        [switch]    $ShowOnlySecrets,


        [Parameter(
            Mandatory = $false
        )]
        [switch]    $ShowExpiredKeys,


        [Parameter(
            Mandatory = $false
        )]
        [ValidateRange(1,720)]
        [int]    $DaysWithinExpiration = 30,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias('ApplicationId', 'ClientId')]
        [string]    $AppId
    )

    BEGIN {
        $ConnectionGraph = Get-MgContext
        if (-not $ConnectionGraph) {
            Write-Error &quot;Please connect to Microsoft Graph&quot; -ErrorAction Stop
        }
        #Adding an extra day to account for hour differences and offsets.
        $DaysWithinExpiration++
    }

    PROCESS {
        try {
            if ($PSBoundParameters.ContainsKey('AppId')) {
                $ApplicationList = Get-MgApplication -Filter &quot;AppId eq '$AppId'&quot; -ErrorAction Stop
                $AppFilter = $true
            } else {
                $ApplicationList = Get-MgApplication -All -Property AppId, DisplayName, PasswordCredentials, KeyCredentials, Id -PageSize 999 -ErrorAction Stop
            }

            #If certs are selected, show certs
            if ($PSBoundParameters.ContainsKey('ShowOnlyCertificates') -or

                #If neither Certs or Secrets are selected show both.
               (-not $PSBoundParameters.ContainsKey('ShowOnlyCertificates') -and
                -not $PSBoundParameters.ContainsKey('ShowOnlySecrets'))) {

                    $CertificateApps  = $ApplicationList | Where-Object {$_.keyCredentials}

                    $CertApp = foreach ($App in $CertificateApps) {
                        foreach ($Cert in $App.keyCredentials) {
                            if ( $Cert.endDateTime -le (Get-Date).AddDays($DaysWithinExpiration) -or ($AppFilter) ) {
                                [PSCustomObject]@{
                                    AppDisplayName      = $App.DisplayName
                                    AppId               = $App.AppId
                                    KeyType             = 'Certificate'
                                    ExpirationDate      = $Cert.EndDateTime
                                    DaysUntilExpiration = (($Cert.EndDateTime) - (Get-Date) | select -ExpandProperty TotalDays) -as [int]
                                    ThumbPrint          = [System.Convert]::ToBase64String($Cert.CustomKeyIdentifier)
                                    Id                  = $App.Id
                                    KeyId               = $Cert.KeyId
                                    Description         = $Cert.DisplayName
                                }
                            }
                        }
                    }

                    if ($PSBoundParameters.ContainsKey('ShowExpiredKeys')) {
                        $CertApp | Sort-Object DaysUntilExpiration
                    } else {
                        $CertApp | Sort-Object DaysUntilExpiration | Where-Object {$_.DaysUntilExpiration -ge 0}
                    }
            }

            #If secrets are selected, show secrets
            if ($PSBoundParameters.ContainsKey('ShowOnlySecrets') -or

                #If neither Certs or Secrets are selected show both.
               (-not $PSBoundParameters.ContainsKey('ShowOnlySecrets') -and
                -not $PSBoundParameters.ContainsKey('ShowOnlyCertificates'))) {

                    $ClientSecretApps = $ApplicationList | Where-Object {$_.passwordCredentials}

                    $SecretApp = foreach ($App in $ClientSecretApps){
                        foreach ($Secret in $App.PasswordCredentials) {
                            if ( $Secret.EndDateTime -le (Get-Date).AddDays($DaysWithinExpiration) -or ($AppFilter) ) {
                                [PSCustomObject]@{
                                    AppDisplayName      = $App.DisplayName
                                    AppId               = $App.AppId
                                    KeyType             = 'ClientSecret'
                                    ExpirationDate      = $Secret.EndDateTime
                                    DaysUntilExpiration = (($Secret.EndDateTime) - (Get-Date) | select -ExpandProperty TotalDays) -as [int]
                                    ThumbPrint          = 'N/A'
                                    Id                  = $App.Id
                                    KeyId               = $Secret.KeyId
                                    Description         = $Secret.DisplayName
                                }
                            }
                        }
                    }

                    if ($PSBoundParameters.ContainsKey('ShowExpiredKeys')) {
                        $SecretApp | Sort-Object DaysUntilExpiration
                    } else {
                        $SecretApp | Sort-Object DaysUntilExpiration | Where-Object {$_.DaysUntilExpiration -ge 0}
                    }
            }
        } catch {
            Write-Error $_.Exception.Message
        }
    }

    END {}
}
</pre>
<div id="scriptparameters" style="scroll-margin-top: 15px;"></div>
<h2>Script Parameters</h2>
<h3>    No Parameters</h3>
<p>DataType: N/A<br />
Description: Gather all apps and display the ones that have a secret or certificate expiring within 30 days.<br />
&nbsp;</p>
<h3>    -ShowOnlyCertificates</h3>
<p>DataType: switch<br />
Description: Only display certificates in the output. Expired keys and secrets will not be shown.<br />
&nbsp;</p>
<h3>    -ShowOnlySecrets</h3>
<p>DataType: switch<br />
Description: Only display secrets in the output. Expired keys and certificates will not be shown.<br />
&nbsp;</p>
<h3>    -ShowExpiredKeys</h3>
<p>DataType: switch<br />
Description: Display certificates or secrets are near expiration or have already expired.<br />
&nbsp;</p>
<h3>    -DaysWithinExpiration</h3>
<p>DataType: integer<br />
Description: Set the time frame to include expiring keys. This is defaulted to 30 days.<br />
&nbsp;</p>
<h3>    -AppId</h3>
<p>DataType: string<br />
Description: Specify an AppId (client Id) to see that specific applications keys.<br />
&nbsp;</p>
<div id="examples" style="scroll-margin-top: 15px;"></div>
<h3>Example 1 &#8211; Calling the script with no parameters</h3>
<p>Since this is a function, you&#8217;ll need to dot source it to load it into memory.  Assuming the file is your desktop you can run.</p>
<pre class="brush: powershell; title: ; notranslate">
. $Home\Desktop\Get-MgApplicationCertificateAndSecretExpiration.ps1
Get-MgApplicationCertificateAndSecretExpiration

</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/01/No-Parameters.jpg" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/01/No-Parameters.jpg" alt="No Parameters" width="902" height="603" class="aligncenter size-full wp-image-4692" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/01/No-Parameters.jpg?v=1673900545 902w, https://thesysadminchannel.com/wp-content/uploads/2023/01/No-Parameters-768x513.jpg?v=1673900545 768w" sizes="(max-width: 902px) 100vw, 902px" /></a><br />
&nbsp;</p>
<p>Using the default output, take a look at the KeyType, ExpirationDate, DaysUntilExpiration and ThumbPrint.  Certificates will display a thumbprint while secrets will not.<br />
&nbsp;</p>
<h3>Example 2 &#8211; Display Only Certificates that are expired or nearing expiration</h3>
<pre class="brush: powershell; title: ; notranslate">
Get-MgApplicationCertificateAndSecretExpiration -ShowOnlyCertificates -ShowExpiredKeys `
| Select-Object AppDisplayName, KeyType, ExpirationDate, DaysUntilExpiration, KeyId, ThumbPrint
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificates-and-Expired-Keys.jpg" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificates-and-Expired-Keys.jpg" alt="Certificates and Expired Keys" width="1015" height="466" class="aligncenter size-full wp-image-4694" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificates-and-Expired-Keys.jpg?v=1673901771 1015w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificates-and-Expired-Keys-768x353.jpg?v=1673901771 768w" sizes="(max-width: 1015px) 100vw, 1015px" /></a><br />
&nbsp;</p>
<p>If you recall <a href="https://thesysadminchannel.com/wp-content/uploads/2023/01/Certificate-Expiration.webp" rel="noopener" target="_blank">Portal screenshot above</a>, our &#8220;App Automation 1&#8221; application had an expired cert with the thumbprint starting in &#8220;2E8972E&#8221; and the Key ID (certificate ID) started with &#8220;6f395fd4&#8221;.  This is the same information we can pull in PowerShell<br />
&nbsp;</p>
<h3>Example 3 &#8211; Display Only secrets that are expiring within 5 days</h3>
<pre class="brush: powershell; title: ; notranslate">
Get-MgApplicationCertificateAndSecretExpiration -ShowOnlySecrets -DaysWithinExpiration 5
| Select-Object AppDisplayName, KeyType, ExpirationDate, DaysUntilExpiration
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/01/Show-only-Secrets.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/01/Show-only-Secrets.png" alt="Show only Secrets" width="989" height="271" class="aligncenter size-full wp-image-4696" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/01/Show-only-Secrets.png?v=1673902300 989w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Show-only-Secrets-768x210.png?v=1673902300 768w" sizes="(max-width: 989px) 100vw, 989px" /></a></p>
<div id="conclusion" style="scroll-margin-top: 15px;"></div>
<h2>Conclusion</h2>
<p>Hopefully this article was able to show you get an application certificate and secret expiration with Graph API.  This is super useful to keep in your automation toolkit since the Azure world is now moving everything to Microsoft Graph.</p>
<p>The post <a href="https://thesysadminchannel.com/get-application-certificate-and-secret-expiration-with-graph-api-powershell/">Get Application Certificate and Secret Expiration with Graph API</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/get-application-certificate-and-secret-expiration-with-graph-api-powershell/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4676</post-id>	</item>
		<item>
		<title>Install Microsoft Graph Module for Azure Automation using PowerShell</title>
		<link>https://thesysadminchannel.com/install-microsoft-graph-module-for-azure-automation-using-powershell/</link>
					<comments>https://thesysadminchannel.com/install-microsoft-graph-module-for-azure-automation-using-powershell/#comments</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Fri, 06 Jan 2023 01:50:41 +0000</pubDate>
				<category><![CDATA[Azure Automation]]></category>
		<category><![CDATA[Graph API]]></category>
		<category><![CDATA[Install Graph API module azure automation]]></category>
		<category><![CDATA[Install Microsoft Graph Module for Azure Automation using PowerShell]]></category>
		<category><![CDATA[Microsoft Graph Module for Azure Automation]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=4662</guid>

					<description><![CDATA[<p>If you&#8217;re familiar with Azure Automation and Graph API, you may have noticed that it may be a bit cumbersome to install the Microsoft.Graph PowerShell module in an Automation account. Since the Graph API module has over 30 sub modules&#8230; <a href="https://thesysadminchannel.com/install-microsoft-graph-module-for-azure-automation-using-powershell/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/install-microsoft-graph-module-for-azure-automation-using-powershell/">Install Microsoft Graph Module for Azure Automation using PowerShell</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>If you&#8217;re familiar with Azure Automation and Graph API, you may have noticed that it may be a bit cumbersome to install the Microsoft.Graph PowerShell module in an Automation account.  Since the Graph API module has over 30 sub modules it&#8217;s a bit of a pain to install the full set manually using the GUI.  Today we&#8217;re going to cover how to <strong>install the Microsoft Graph Module for Azure Automation using PowerShell</strong>.</p>
<div id="tableofcontents">
<h2>Table Of Contents</h2>
<ul>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#installmodule">Install Microsoft Graph Module for Azure Automation using PowerShell</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<div id="requirements" style="scroll-margin-top: 15px;"></div>
<h2>Requirements</h2>
<p>Before I get to the script, there are a few things that need to be in place before we&#8217;re able to successfully install the module. Let&#8217;s cover that now:</p>
<ul>
<li>Az PowerShell Module</li>
<li>Permissions to install any module</li>
</ul>
<div id="installmodule" style="scroll-margin-top: 15px;"></div>
<h2>Install Microsoft Graph Module for Azure Automation using PowerShell</h2>
<p>Here is the script I wrote to be able to install and update the Microsoft.Graph module for an Automation account.  You can always manually check for the latest version on the <a href="https://www.powershellgallery.com/packages/Microsoft.Graph/" rel="noopener" target="_blank">PowerShell gallery</a>.</p>
<pre class="brush: powershell; title: ; notranslate">
#Before running the script, be sure you're on the right subscription
Set-AzContext -SubscriptionId $SubscriptionId
$ResourceGroup = 'rg-resourcegroupname'
$AutomationAccount = 'automationaccountname'
[System.Collections.Generic.List[Object]]$InstalledModules = @()

#Get top level graph module
$GraphModule = Find-Module Microsoft.Graph
$DependencyList = $GraphModule | select -ExpandProperty Dependencies | ConvertTo-Json | ConvertFrom-Json
$ModuleVersion = $GraphModule.Version

#Since we know the authentication module is a dependency, let us get that one first
$ModuleName = 'Microsoft.Graph.Authentication'
$ContentLink = &quot;https://www.powershellgallery.com/api/v2/package/$ModuleName/$ModuleVersion&quot;
New-AzAutomationModule -ResourceGroupName $ResourceGroup -AutomationAccountName $AutomationAccount -Name $ModuleName -ContentLinkUri $ContentLink -ErrorAction Stop | Out-Null
do {
    Start-Sleep 20
    $Status = Get-AzAutomationModule -ResourceGroupName $ResourceGroup -AutomationAccountName $AutomationAccount -Name $ModuleName | select -ExpandProperty ProvisioningState
} until ($Status -in ('Failed','Succeeded'))

if ($Status -eq 'Succeeded') {
    $InstalledModules.Add($ModuleName)

    foreach ($Dependency in $DependencyList) {
        $ModuleName = $Dependency.Name
        if ($ModuleName -notin $InstalledModules) {
            $ContentLink = &quot;https://www.powershellgallery.com/api/v2/package/$ModuleName/$ModuleVersion&quot;
            New-AzAutomationModule -ResourceGroupName $ResourceGroup -AutomationAccountName $AutomationAccount -ContentLinkUri $ContentLink -Name $ModuleName -ErrorAction Stop | Out-Null
            sleep 3
        }
    }

    $LoopIndex = 0
    do {
        foreach ($Dependency in $DependencyList) {
            $ModuleName = $Dependency.Name
            if ($ModuleName -notin $InstalledModules) {
                $Status = Get-AzAutomationModule -ResourceGroupName $ResourceGroup -AutomationAccountName $AutomationAccount -Name $ModuleName -ErrorAction SilentlyContinue | select -ExpandProperty ProvisioningState
                sleep 3
                if ($Status -in ('Failed','Succeeded')) {
                    if ($Status -eq 'Succeeded') {
                        $InstalledModules.Add($ModuleName)
                    }

                    [PSCustomObject]@{
                        Status           = $Status
                        ModuleName       = $ModuleName
                        ModulesInstalled = $InstalledModules.Count
                    }
                }
            }
        }
        $LoopIndex++
    } until (($InstalledModules.Count -ge $GraphModule.Dependencies.count) -or ($LoopIndex -ge 10))
}



</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Authentication-Module.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Authentication-Module.png" alt="Install Authentication Module" width="1200" height="438" class="aligncenter size-full wp-image-4666" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Authentication-Module.png 1200w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Authentication-Module-1024x374.png 1024w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Authentication-Module-768x280.png 768w" sizes="(max-width: 1200px) 100vw, 1200px" /></a></p>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules.png" alt="Microsoft Graph Module for Azure Automation" width="1383" height="675" class="aligncenter size-full wp-image-4668" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules.png 1383w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules-1024x500.png 1024w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules-768x375.png 768w" sizes="(max-width: 1383px) 100vw, 1383px" /></a></p>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules-Start-PowerShell.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules-Start-PowerShell.png" alt="Microsoft Graph Module for Azure Automation" width="1394" height="556" class="aligncenter size-full wp-image-4667" srcset="https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules-Start-PowerShell.png 1394w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules-Start-PowerShell-1024x408.png 1024w, https://thesysadminchannel.com/wp-content/uploads/2023/01/Install-Graph-Modules-Start-PowerShell-768x306.png 768w" sizes="(max-width: 1394px) 100vw, 1394px" /></a></p>
<div id="conclusion" style="scroll-margin-top: 15px;"></div>
<h2>Conclusion</h2>
<p>And just like that, we were able to install the Microsoft Graph Module for Azure Automation. One thing to note, This method only supports PowerShell version 5.1 at the moment so if you want to install the module for PowerShell 7 and later, you will still need to do that the manual way.<br />
&nbsp;</p>
<p>Since you&#8217;re now using Graph API with Azure Automation, here is a great article to <a href="https://thesysadminchannel.com/graph-api-using-a-managed-identity-in-an-automation-runbook/" rel="noopener" target="_blank">use Managed Identities in your Automation Runbooks</a> so you no longer have to worry about secrets or certificates in your code.</p>
<p>The post <a href="https://thesysadminchannel.com/install-microsoft-graph-module-for-azure-automation-using-powershell/">Install Microsoft Graph Module for Azure Automation using PowerShell</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/install-microsoft-graph-module-for-azure-automation-using-powershell/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4662</post-id>	</item>
		<item>
		<title>Graph API using a Managed Identity in an Automation Runbook</title>
		<link>https://thesysadminchannel.com/graph-api-using-a-managed-identity-in-an-automation-runbook/</link>
					<comments>https://thesysadminchannel.com/graph-api-using-a-managed-identity-in-an-automation-runbook/#comments</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Fri, 30 Dec 2022 23:47:54 +0000</pubDate>
				<category><![CDATA[Azure]]></category>
		<category><![CDATA[Azure Automation]]></category>
		<category><![CDATA[Graph API]]></category>
		<category><![CDATA[Connect to Graph API using a Managed Identity in an Automation Runbook]]></category>
		<category><![CDATA[Connect to Graph Automation Runbook]]></category>
		<category><![CDATA[Graph API managed identity]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=4632</guid>

					<description><![CDATA[<p>In a previous post, I laid out detailed steps on how to connect to Microsoft Graph API using PowerShell. This is using the PowerShell SDK running on a machine that&#8217;s not in Azure. This method uses a client secret or&#8230; <a href="https://thesysadminchannel.com/graph-api-using-a-managed-identity-in-an-automation-runbook/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/graph-api-using-a-managed-identity-in-an-automation-runbook/">Graph API using a Managed Identity in an Automation Runbook</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>In a previous post, I laid out detailed steps on <a href="https://thesysadminchannel.com/how-to-connect-to-microsoft-graph-api-using-powershell/" rel="noopener" target="_blank">how to connect to Microsoft Graph API using PowerShell</a>.  This is using the PowerShell SDK running on a machine that&#8217;s not in Azure.  This method uses a client secret or certificate along with a App registration.  This is a solid method for this use case, however, managing certificates or especially secrets can be a bit of a pain. With that said, what if we wanted to take it a step further and not use secrets and/or certificates at all?  This article is going to focus on how to connect to Graph API using a Managed Identity in an Automation Runbook.</p>
<div id="tableofcontents">
<h2>Table Of Contents</h2>
<ul>
<li><a href="#managedidentity">What is a Managed Identity?</a></li>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#enableidentity">Enable a Managed Identity for Azure Automation</a></li>
<li><a href="#permissions">Grant Permissions to a Managed Identity</a></li>
<li><a href="#connectgraphapi">Connect to Graph API using a Managed Identity in an Automation Runbook</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<div id="managedidentity" style="scroll-margin-top: 15px;"></div>
<h2>What is a Managed Identity?</h2>
<p>An Azure managed identity is a feature of Azure Active Directory that enables Azure services to authenticate to cloud resources securely. It eliminates the need for you to store and manage the credentials for your applications and services, and helps you to follow the principle of least privilege by providing just-in-time access to resources.<br />
&nbsp;</p>
<p>There are two types of managed identities:</p>
<li><strong>System-assigned managed identities</strong></li>
<ul>
	These identities are created when you enable the managed identity feature for an Azure resource and are tied to the lifecycle of that resource. When the resource is deleted, the managed identity is also deleted.
</ul>
<p>&nbsp;</p>
<li><strong>User-assigned managed identities</strong></li>
<ul>
These identities are created independently of an Azure resource and can be assigned to multiple resources. This allows you to manage the identity in a central location and reuse it across multiple resources.
</ul>
<p>&nbsp;</p>
<p>Using a managed identity can help you secure your application and make it easier to manage the authentication process since you no longer have to manage secrets or credentials in your code. For this article, we&#8217;re going to focus on using a system-assigned managed identity that&#8217;s created in the Azure Automation account.<br />
&nbsp;</p>
<p>Check out the Microsoft docs to learn more about <a href="https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview" rel="noopener" target="_blank">managed identities</a></p>
<div id="requirements" style="scroll-margin-top: 15px;"></div>
<h2>Requirements</h2>
<p>In order to use an Azure managed identity, you need to ensure you meet the following requirements.</p>
<ul>
<li>An Azure subscription</li>
<li>A Resource Group with Owner or user access administrator roles</li>
<li>An Azure Automation Account</li>
<li>The managed identity must be in the same region as the Automation Runbook</li>
<ul>
<li>This also applies to other Azure resources as well</li>
</ul>
</ul>
<div id="enableidentity" style="scroll-margin-top: 15px;"></div>
<h2>Enable a Managed Identity for Azure Automation</h2>
<p>Now that we have the requirements in place, let&#8217;s go ahead and start the process to <strong>enable a managed identity for Azure Automation</strong>. We&#8217;re going to assume you have already created an Automation account in your subscription.</p>
<p>Within your automation account:</p>
<ul>
<li>Click on Identity on the left pane</li>
<li>Ensure the System assigned tab is selected</li>
<li>Toggle the status from &#8220;Off&#8221; to &#8220;On&#8221;</li>
<li>Copy the object (principal) Id to a notepad.  We&#8217;ll need it later</li>
<li>Click save</li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/Enable-a-managed-identity-for-Azure-Automation.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/Enable-a-managed-identity-for-Azure-Automation.png" alt="Enable a managed identity for Azure Automation" width="1152" height="679" class="aligncenter size-full wp-image-4645" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/Enable-a-managed-identity-for-Azure-Automation.png?v=1672279715 1152w, https://thesysadminchannel.com/wp-content/uploads/2022/12/Enable-a-managed-identity-for-Azure-Automation-1024x604.png?v=1672279715 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/12/Enable-a-managed-identity-for-Azure-Automation-125x75.png?v=1672279715 125w, https://thesysadminchannel.com/wp-content/uploads/2022/12/Enable-a-managed-identity-for-Azure-Automation-768x453.png?v=1672279715 768w" sizes="(max-width: 1152px) 100vw, 1152px" /></a></p>
<div id="permissions" style="scroll-margin-top: 15px;"></div>
<h2>Grant Permissions to a Managed Identity</h2>
<p>When a managed identity is created, it starts off with a clean slate and no permissions.  This means that you will need to grant permissions to the resources that it needs to interact with.<br />
&nbsp;</p>
<p>In our example, we need to grant the managed identity from our Automation account access to read as well as run jobs from the Azure Automation Runbook. We can do this from the &#8220;Azure role assignments&#8221; button on the Identity blade, but I like to use PowerShell because it&#8217;s a lot faster.<br />
&nbsp;</p>
<pre class="brush: powershell; title: ; notranslate">
  $ServicePrincipalId = '900d23ex-xxxx-xxxx-xxxx-xxxxxxxxxxxx' #Copied from the Identity blade above
  $ResourceGroupName = 'rg-azure-automation'
  New-AzRoleAssignment -ObjectId $ServicePrincipalId -ResourceGroupName $ResourceGroupName -RoleDefinitionName Reader
  New-AzRoleAssignment -ObjectId $ServicePrincipalId -ResourceGroupName $ResourceGroupName -RoleDefinitionName 'Automation Job Operator'
  New-AzRoleAssignment -ObjectId $ServicePrincipalId -ResourceGroupName $ResourceGroupName -RoleDefinitionName 'Automation Runbook Operator'
</pre>
<p>&nbsp;</p>
<p>More importantly, we need to know how to grant the managed identity permissions to Graph API (most common) or other app role resources that you may need.  Since Azure Automation Runbooks don&#8217;t require a secret or certificate to connect to Graph API, this is ideal and the most secured way since we&#8217;re letting Azure handle all the authentication process in the cloud.<br />
&nbsp;</p>
<pre class="brush: powershell; title: ; notranslate">
#New Service Principal Permissions using Azure AD module
$ServicePrincipalId = '900d23ex-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
$GraphResource = Get-AzureADServicePrincipal -Filter &quot;AppId eq '00000003-0000-0000-c000-000000000000'&quot;
$Permission = $GraphResource.AppRoles | Where-Object {$_.value -eq 'User.Read.All'}
New-AzureADServiceAppRoleAssignment -ObjectId $ServicePrincipalId -PrincipalId $ServicePrincipalId -Id $Permission.Id -ResourceId $GraphResource.ObjectId


#New Service Principal Permissions using Graph API
$ServicePrincipalId = '900d23ex-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
$GraphResource = Get-MgServicePrincipal -Filter &quot;AppId eq '00000003-0000-0000-c000-000000000000'&quot;
$Permission = $GraphResource.AppRoles | Where-Object {$_.value -eq 'User.Read.All'}
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ServicePrincipalId -PrincipalId $ServicePrincipalId -AppRoleId $Permission.Id -ResourceId $GraphResource.Id
</pre>
<p>&nbsp;</p>
<p>To view the permissions that are currently granted for a Service Principal, you can go to</p>
<ul>
<li>Azure AD</li>
<li>Enterprise Applications</li>
<li>Application Type == Managed Identity</li>
<li>select the Service Principal</li>
<li>select Permissions on the left scroll panel</li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/Graph-API-permissions-for-managed-identity.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/Graph-API-permissions-for-managed-identity.png" alt="Graph API permissions for managed identity" width="1705" height="880" class="aligncenter size-full wp-image-4647" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/Graph-API-permissions-for-managed-identity.png?v=1672282758 1705w, https://thesysadminchannel.com/wp-content/uploads/2022/12/Graph-API-permissions-for-managed-identity-1024x529.png?v=1672282758 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/12/Graph-API-permissions-for-managed-identity-768x396.png?v=1672282758 768w, https://thesysadminchannel.com/wp-content/uploads/2022/12/Graph-API-permissions-for-managed-identity-1536x793.png?v=1672282758 1536w" sizes="(max-width: 1705px) 100vw, 1705px" /></a></p>
<div id="connectgraphapi" style="scroll-margin-top: 15px;"></div>
<h2>Connect to Graph API using a Managed Identity in an Automation Runbook</h2>
<p>Alright folks!  We&#8217;ve added permissions to the managed identity to access resources in our resource group as well as added permissions to access/modify objects in Graph API.  All that&#8217;s left is actually connecting to Graph API.<br />
&nbsp;</p>
<p>To make this happen, we&#8217;ll first need to have the Microsoft.Graph PowerShell module added under the modules for your automation account. If you&#8217;re using a Hybrid worker account and running the script on-premises, you&#8217;ll need to install the module on the on-premises machine that&#8217;s running the script.<br />
&nbsp;</p>
<p>In your Automation Runbook, edit the page and add these few lines.  If you&#8217;re using parameters, be sure to add it after the param block. When you&#8217;re ready to publish, hit the publish button so it saves and publishes the script.</p>
<pre class="brush: powershell; title: ; notranslate">
try {
    #Get the token using a managed identity and connect to graph using that token
    Connect-AzAccount -Identity -ErrorAction Stop | Out-Null
    $AccessToken = Get-AzAccessToken -ResourceTypeName MSGraph -ErrorAction Stop | select -ExpandProperty Token
    Connect-Graph -AccessToken $AccessToken -ErrorAction Stop | Out-Null

    Select-MgProfile -Name Beta
    Get-MgUser -UserId buzz@thesysadminchannel.com | select DisplayName, UserPrincipalName, UserType, AccountEnabled
} catch {
    Write-Error $_.Exception.Message -ErrorAction Stop
}     

</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/Connect-to-Graph-API-using-a-Managed-Identity-in-an-Automation-Runbook-01.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/Connect-to-Graph-API-using-a-Managed-Identity-in-an-Automation-Runbook-01.png" alt="Connect to Graph API using a Managed Identity in an Automation Runbook" width="1145" height="361" class="aligncenter size-full wp-image-4651" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/Connect-to-Graph-API-using-a-Managed-Identity-in-an-Automation-Runbook-01.png?v=1672427916 1145w, https://thesysadminchannel.com/wp-content/uploads/2022/12/Connect-to-Graph-API-using-a-Managed-Identity-in-an-Automation-Runbook-01-1024x323.png?v=1672427916 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/12/Connect-to-Graph-API-using-a-Managed-Identity-in-an-Automation-Runbook-01-768x242.png?v=1672427916 768w" sizes="(max-width: 1145px) 100vw, 1145px" /></a><br />
&nbsp;</p>
<p>We&#8217;ve published the script so let&#8217;s trigger it.  You can trigger this by using the Start button in the Runbook, or trigger it from a Logic App. Either way, once it has completed running, it should display the output in the job details.<br />
&nbsp;</p>
<p>If you don&#8217;t see the output, be sure to check the Errors or Exception tabs to see where it failed.</p>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/Automation-Runbook-output.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/Automation-Runbook-output.png" alt="Automation Runbook output" width="861" height="484" class="aligncenter size-full wp-image-4653" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/Automation-Runbook-output.png?v=1672429087 861w, https://thesysadminchannel.com/wp-content/uploads/2022/12/Automation-Runbook-output-768x432.png?v=1672429087 768w" sizes="(max-width: 861px) 100vw, 861px" /></a></p>
<div id="conclusion" style="scroll-margin-top: 15px;"></div>
<h2>Conclusion</h2>
<p>Hopefully this article was able to help you connect to Graph API using a Managed Identity in an Automation Runbook.  This method works great if you&#8217;re running the Runbook from Azure as well as a Hybrid Worker machine (on-premises machine).</p>
<p>The post <a href="https://thesysadminchannel.com/graph-api-using-a-managed-identity-in-an-automation-runbook/">Graph API using a Managed Identity in an Automation Runbook</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/graph-api-using-a-managed-identity-in-an-automation-runbook/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4632</post-id>	</item>
		<item>
		<title>New-MgGroup: Create A Group in Graph API</title>
		<link>https://thesysadminchannel.com/new-mggroup-create-a-group-in-graph-api/</link>
					<comments>https://thesysadminchannel.com/new-mggroup-create-a-group-in-graph-api/#respond</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Mon, 12 Dec 2022 18:08:54 +0000</pubDate>
				<category><![CDATA[Azure]]></category>
		<category><![CDATA[Graph API]]></category>
		<category><![CDATA[Create A Group in Graph API]]></category>
		<category><![CDATA[create azure ad group powershell]]></category>
		<category><![CDATA[create group graph api REST API]]></category>
		<category><![CDATA[New-MgGroup]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=4518</guid>

					<description><![CDATA[<p>Groups are essential to the management of resources in any platform and it&#8217;s helpful to use them instead of using individual users. Today we&#8217;re going to cover how to create a group in Graph API using the PowerShell SDK for&#8230; <a href="https://thesysadminchannel.com/new-mggroup-create-a-group-in-graph-api/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/new-mggroup-create-a-group-in-graph-api/">New-MgGroup: Create A Group in Graph API</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Groups are essential to the management of resources in any platform and it&#8217;s helpful to use them instead of using individual users.  Today we&#8217;re going to cover <strong>how to create a group in Graph API</strong> using the PowerShell SDK for us IT Pros.  This specifically goes over the usage of <strong>New-MgGroup</strong> and the various ways you can use it.</p>
<div id="tableofcontents">
<h2>Table Of Contents</h2>
<ul>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#creategrouppowershell">Create A Group in Graph API with New-MgGroup</a></li>
<li><a href="#creategroupowner">Create A Security Group with an Owner</a></li>
<li><a href="#creategroupmember">Create A Group with an Owner and Members</a></li>
<li><a href="#createdyanmicgroup">Create A Dynamic Security Group with Membership Rules</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<div id="requirements" style="scroll-margin-top: 15px;"></div>
<h2>Requirements</h2>
<p>In order to successfully create groups in Graph API, we&#8217;ll need to ensure we have the right permissions needed.  Let&#8217;s touch a bit on what those might be.</p>
<ul>
<li><strong>User Administrator</strong>, <strong>Groups Administrator</strong> or <strong>Global Administrator</strong> Azure AD Role(s)</li>
<li><strong>Microsoft.Graph</strong> PowerShell SDK Module (if not using the REST API)</li>
<li>Graph API Scopes:</li>
<ul>
<li>Delegated: Group.ReadWrite.All, Directory.ReadWrite.All</li>
<li>Application: Group.Create, Group.ReadWrite.All, Directory.ReadWrite.All</li>
</ul>
</ul>
<div id="creategrouppowershell" style="scroll-margin-top: 15px;"></div>
<h2>Create A Group in Graph API with New-MgGroup</h2>
<p>When creating groups in Azure AD, 99% of the time I create security enabled groups because I use them for providing access to specific resources. When I need to create groups with an email address, I use Distribution Lists (or Mail-enabled Security groups) in Exchange.  Personally, I am not a fan of M365 (unified groups) but that&#8217;s a topic for another day.<br />
&nbsp;</p>
<p>Let&#8217;s dig into the code for creating a security group in Azure AD using Graph API.</p>
<pre class="brush: powershell; title: ; notranslate">
$GroupParam = @{
     DisplayName = &quot;SG-SecurityNoOwnerNoMember&quot;
     GroupTypes = @(
     )
     SecurityEnabled     = $true
     IsAssignableToRole  = $false
     MailEnabled         = $false
     MailNickname        = (New-Guid).Guid.Substring(0,10)
}

New-MgGroup -BodyParameter $GroupParam
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityNoOwnerNoMember-1.jpg" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityNoOwnerNoMember-1.jpg" alt="SG-SecurityNoOwnerNoMember" width="906" height="399" class="aligncenter size-full wp-image-4527" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityNoOwnerNoMember-1.jpg?v=1670439588 906w, https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityNoOwnerNoMember-1-768x338.jpg?v=1670439588 768w" sizes="(max-width: 906px) 100vw, 906px" /></a></p>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityNoOwnerNoMemberProperties.jpg" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityNoOwnerNoMemberProperties.jpg" alt="SG-SecurityNoOwnerNoMemberProperties" width="1025" height="583" class="aligncenter size-full wp-image-4529" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityNoOwnerNoMemberProperties.jpg?v=1670439859 1025w, https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityNoOwnerNoMemberProperties-768x437.jpg?v=1670439859 768w" sizes="(max-width: 1025px) 100vw, 1025px" /></a></p>
<div id="creategroupowner" style="scroll-margin-top: 15px;"></div>
<h2>Create A Security Group with an Owner</h2>
<p>We got the basics of creating a security group by using the PowerShell SDK and Graph API, but let&#8217;s add on to this by adding an owner to the group.  You can add a Service Principal or a user account as an owner.<br />
&nbsp;</p>
<p>If you want to add a Service Principal, you&#8217;ll need to know the Service Principal Id so we can bind it to the parameters.  If you&#8217;re looking to add a user, you can use the UserPrincipalName, or UserId.  Let&#8217;s do this now.</p>
<pre class="brush: powershell; title: ; notranslate">
$GroupParam = @{
     DisplayName = &quot;SG-SecurityGroupWithOwner&quot;
     GroupTypes = @(
     )
     SecurityEnabled     = $true
     IsAssignableToRole  = $false
     MailEnabled         = $false
     MailNickname        = (New-Guid).Guid.Substring(0,10)
     &quot;Owners@odata.bind&quot; = @(
         &quot;https://graph.microsoft.com/v1.0/me&quot;,
         &quot;https://graph.microsoft.com/v1.0/users/luke@thesysadminchannel.com&quot;,
         &quot;https://graph.microsoft.com/v1.0/users/647e9c5e-xxxx-xxxx-xxxx-xxxxxxxxxxxx&quot;
         &quot;https://graph.microsoft.com/v1.0/servicePrincipals/50ded543-xxxx-xxxx-xxxx-xxxxxxxxxxxx&quot;
     )
}
PS C:\&gt; New-MgGroup -BodyParameter $GroupParam
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwner.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwner.png" alt="SG-SecurityGroupwithOwner" width="947" height="482" class="aligncenter size-full wp-image-4531" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwner.png?v=1670440938 947w, https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwner-768x391.png?v=1670440938 768w" sizes="(max-width: 947px) 100vw, 947px" /></a></p>
<div id="creategroupmember" style="scroll-margin-top: 15px;"></div>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerProperties.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerProperties.png" alt="SG-SecurityGroupwithOwnerProperties" width="852" height="389" class="aligncenter size-full wp-image-4532" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerProperties.png?v=1670441205 852w, https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerProperties-768x351.png?v=1670441205 768w" sizes="(max-width: 852px) 100vw, 852px" /></a></p>
<h2>Create A Group with an Owner and Members</h2>
<p>Next up, let&#8217;s create a group with owners and a couple of members.</p>
<pre class="brush: powershell; title: ; notranslate">
$GroupParam = @{
     DisplayName = &quot;SG-SecurityGroupWithOwnerAndMembers&quot;
     GroupTypes = @(
     )
     SecurityEnabled     = $true
     IsAssignableToRole  = $false
     MailEnabled         = $false
     MailNickname        = (New-Guid).Guid.Substring(0,10)
     &quot;Owners@odata.bind&quot; = @(
         &quot;https://graph.microsoft.com/v1.0/me&quot;,
         &quot;https://graph.microsoft.com/v1.0/users/luke@thesysadminchannel.com&quot;
     )
     &quot;Members@odata.bind&quot; = @(
         &quot;https://graph.microsoft.com/v1.0/me&quot;,
         &quot;https://graph.microsoft.com/v1.0/users/buzz@thesysadminchannel.com&quot;
     )
 }
New-MgGroup -BodyParameter $GroupParam
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerAndMembers.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerAndMembers.png" alt="SG-SecurityGroupwithOwnerAndMembers" width="981" height="518" class="aligncenter size-full wp-image-4537" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerAndMembers.png?v=1670442290 981w, https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerAndMembers-768x406.png?v=1670442290 768w" sizes="(max-width: 981px) 100vw, 981px" /></a></p>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerAndMembersProperties.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-SecurityGroupwithOwnerAndMembersProperties.png" alt="SG-SecurityGroupwithOwnerAndMembersProperties" width="820" height="398" class="aligncenter size-full wp-image-4538" /></a></p>
<div id="createdyanmicgroup" style="scroll-margin-top: 15px;"></div>
<h2>Create A Dynamic Security Group with Membership Rules</h2>
<p>Last and certainly not least, let&#8217;s get started with creating dynamic groups to add members with specific criteria.  In my case, I am just going to add a statement where the account is enabled and the UPN is mine.</p>
<pre class="brush: powershell; title: ; notranslate">
$GroupParam = @{
     DisplayName = &quot;SG-DynamicSecurityGroup&quot;
     GroupTypes = @(
         'DynamicMembership'
     )
     SecurityEnabled     = $true
     IsAssignableToRole  = $false
     MailEnabled         = $false
     membershipRuleProcessingState = 'On'
     MembershipRule = '(user.accountEnabled -eq true) and (user.userPrincipalName -eq &quot;paul@thesysadminchannel.com&quot;)'
     MailNickname        = (New-Guid).Guid.Substring(0,10)
     &quot;Owners@odata.bind&quot; = @(
         &quot;https://graph.microsoft.com/v1.0/me&quot;
     )
 }

New-MgGroup -BodyParameter $GroupParam
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-DynamicSecurityGroup.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-DynamicSecurityGroup.png" alt="SG-DynamicSecurityGroup" width="1099" height="517" class="aligncenter size-full wp-image-4541" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-DynamicSecurityGroup.png?v=1670442984 1099w, https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-DynamicSecurityGroup-1024x482.png?v=1670442984 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-DynamicSecurityGroup-768x361.png?v=1670442984 768w" sizes="(max-width: 1099px) 100vw, 1099px" /></a></p>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-DynamicSecurityGroupProperties.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-DynamicSecurityGroupProperties.png" alt="SG-DynamicSecurityGroupProperties" width="975" height="556" class="aligncenter size-full wp-image-4542" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-DynamicSecurityGroupProperties.png?v=1670443009 975w, https://thesysadminchannel.com/wp-content/uploads/2022/12/SG-DynamicSecurityGroupProperties-768x438.png?v=1670443009 768w" sizes="(max-width: 975px) 100vw, 975px" /></a></p>
<div id="conclusion" style="scroll-margin-top: 15px;"></div>
<h2>Conclusion</h2>
<p>Hopefully this article was able to show you how to create a group in Graph API using the New-MgGroup cmdlet that comes with the Microsoft.Graph PowerShell SDK.</p>
<p>The post <a href="https://thesysadminchannel.com/new-mggroup-create-a-group-in-graph-api/">New-MgGroup: Create A Group in Graph API</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/new-mggroup-create-a-group-in-graph-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4518</post-id>	</item>
		<item>
		<title>Exchange Online Certificate Based Authentication</title>
		<link>https://thesysadminchannel.com/exchange-online-certificate-based-authentication/</link>
					<comments>https://thesysadminchannel.com/exchange-online-certificate-based-authentication/#comments</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Sun, 06 Nov 2022 19:38:40 +0000</pubDate>
				<category><![CDATA[Exchange Online]]></category>
		<category><![CDATA[Powershell]]></category>
		<category><![CDATA[app-only authentication exchange online]]></category>
		<category><![CDATA[azure app registration certificate authentication]]></category>
		<category><![CDATA[certificate-based authentication for exchange online remote powershell]]></category>
		<category><![CDATA[Connect to Exchange Online Certificate Based Authentication]]></category>
		<category><![CDATA[connect-exchange online certificate thumbprint]]></category>
		<category><![CDATA[exchange certificate based authentication]]></category>
		<category><![CDATA[Exchange Online certificate-based authentication]]></category>
		<category><![CDATA[office 365 certificate-based authentication]]></category>
		<category><![CDATA[remote powershell using certificate-based authentication]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=4477</guid>

					<description><![CDATA[<p>As a Systems Engineer I am constantly looking for ways to improve processes as well as look for ways to automate everything I possibly can. As a general rule of thumb, I try to automate myself out of a job&#8230; <a href="https://thesysadminchannel.com/exchange-online-certificate-based-authentication/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/exchange-online-certificate-based-authentication/">Exchange Online Certificate Based Authentication</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>As a Systems Engineer I am constantly looking for ways to improve processes as well as look for ways to automate everything I possibly can.  As a general rule of thumb, I try to automate myself out of a job so everything can run silky smooth should I ever get hit by a bus.  Since I work primary in Microsoft 365 and Azure AD, I thought it would be great to share what I&#8217;ve learned in order to use that automation for Exchange Online. With that said, this article is going to be geared around <strong>Exchange Online Certificate Based Authentication</strong> and the steps to go 100% Passwordless.</p>
<div id="tableofcontents">
<h2>Table Of Contents</h2>
<ul>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#createcertificate">Create a Self-Signed Certificate</a></li>
<li><a href="#appregistration">Create an Azure App Registration and Service Principal</a></li>
<li><a href="#addexchangerole">Add Exchange Administrator Role</a></li>
<li><a href="#connecttoapp">Connect to Exchange Online using the Azure Application</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<div id="requirements" style="scroll-margin-top: 15px;"></div>
<h2>Requirements</h2>
<p>In order to set this up without failure, there are a few things needed to get you on your way to using Exchange Online certificate based authentication.  Let&#8217;s cover what&#8217;s needed right now.<br />
&nbsp;</p>
<ul>
<li>A certificate, either self signed or one issued by PKI</li>
<li>Azure Application Administrator or Global Administrator</li>
<li>Privilege Role Administrator or Global Administrator</li>
<li>Exchange Online Management PowerShell module</li>
</ul>
<p>&nbsp;</p>
<p>Above are the requirements to allow you to connect to Exchange Online using certificates.  I manage Exchange Online using PowerShell so I added that as well.  If you&#8217;re looking for instructions on how to get that installed, check out this article to <a href="https://thesysadminchannel.com/how-to-install-exchange-online-powershell-module/" rel="noopener" target="_blank">install the Exchange Online Management module for PowerShell</a>.</p>
<div id="createcertificate" style="scroll-margin-top: 15px;"></div>
<h2>Create a Self-Signed Certificate</h2>
<p>First things first, I thought it would be best to start off by creating the self-signed certificate to get the ball rolling.  If possible, I would recommend using a certificate issued by a public key infrastructure (PKI). The reason for that is because we know we can trust it, it is inherently more secure, and we can also revoke the cert should the situation call for it. The problem is not every environment has a PKI setup (my lab included).<br />
&nbsp;</p>
<p>As mentioned, we don&#8217;t have a PKI in our environment so we&#8217;ll make due with a self signed certificate. Luckily, Azure does support self signed certs so let&#8217;s get that created within PowerShell.<br />
With PowerShell open, enter in the following:<br />
&nbsp;</p>
<pre class="brush: powershell; title: ; notranslate">
#splatting for human readability
$CertParam = @{
    'KeyAlgorithm'      = 'RSA'
    'KeyLength'         = 2048
    'KeyExportPolicy'   = 'NonExportable'
    'DnsName'           = 'server.thesysadminchannel.com'
    'FriendlyName'      = 'Exchange Online Automation App'
    'CertStoreLocation' = 'Cert:\CurrentUser\My\'
    'NotAfter'          = (Get-Date).AddYears(1)
}
 
#Creating self signed cert with parameters from above.
$Cert = New-SelfSignedCertificate @CertParam
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/Self-Signed-Certificate.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/Self-Signed-Certificate.png" alt="Self Signed Certificate" width="960" height="468" class="aligncenter size-full wp-image-4481" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/Self-Signed-Certificate.png?v=1667715233 960w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Self-Signed-Certificate-768x374.png?v=1667715233 768w" sizes="(max-width: 960px) 100vw, 960px" /></a><br />
&nbsp;</p>
<p>The above parameters do not allow you to export the certificate to another machine.  I should also note that this is saving the certificate under the user context.  If you want to store the certificate under the local machine context, you will need to run PowerShell as an administrator anytime you to connect.  Allowing it under the local machine certificate store means other administrators on the machine would also be able to connect.  So just be aware.<br />
&nbsp;</p>
<p>Now that we have the cert created, let&#8217;s export it so we can upload it to Azure when we create our application.</p>
<pre class="brush: powershell; title: ; notranslate">
#Since we captured the output to the $Cert variable in our previous step.
#We will use that to specify the cert parameter. 
#The .cer file will exported to the user's desktop.
 
Export-Certificate -Cert $Cert -FilePath $Home\Desktop\ExchangeOnlineAutomation.cer
</pre>
<div id="appregistration" style="scroll-margin-top: 15px;"></div>
<h2>Create an Azure App Registration and Service Principal</h2>
<p>To get started, we need to make sure we have the proper rights to get the application created.  This is where you will need an Azure AD Application administrator (or Global administrator).<br />
&nbsp;</p>
<p>Within Azure AD:</p>
<ul>
<li>Navigate to <strong>App registrations</strong> → <strong>New registration</strong></li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/New-App-Registration.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/New-App-Registration.png" alt="New App Registration" width="876" height="395" class="aligncenter size-full wp-image-4478" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/New-App-Registration.png?v=1667705064 876w, https://thesysadminchannel.com/wp-content/uploads/2022/11/New-App-Registration-768x346.png?v=1667705064 768w" sizes="(max-width: 876px) 100vw, 876px" /></a><br />
&nbsp;</p>
<ul>
<li>Name your application accordingly.  I&#8217;ve named mine <strong>Exchange Online Automation</strong></li>
<li>Select Accounts in this organizational directory only (Single tenant)</li>
<li>Leave the Redirect URI empty</li>
<li>Click Register to create the app</li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/Register-new-app.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/Register-new-app.png" alt="Register new app" width="1238" height="808" class="aligncenter size-full wp-image-4480" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/Register-new-app.png?v=1667705806 1238w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Register-new-app-1024x668.png?v=1667705806 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Register-new-app-768x501.png?v=1667705806 768w" sizes="(max-width: 1238px) 100vw, 1238px" /></a><br />
&nbsp;</p>
<p>With your app now created:</p>
<ul>
<li>Navigate to Certificates &#038; secrets</li>
<li>Click the certificates tab</li>
<li>Click Upload certificate</li>
<li>Click the folder icon and browse to your desktop to select the exported cert</li>
<li>Click Add</li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/Upload-Certificate-to-Azure-App.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/Upload-Certificate-to-Azure-App.png" alt="Upload Certificate to Azure App" width="1475" height="833" class="aligncenter size-full wp-image-4484" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/Upload-Certificate-to-Azure-App.png?v=1667717494 1475w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Upload-Certificate-to-Azure-App-1024x578.png?v=1667717494 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Upload-Certificate-to-Azure-App-768x434.png?v=1667717494 768w" sizes="(max-width: 1475px) 100vw, 1475px" /></a></p>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/Certificate-Setting-for-Azure-App.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/Certificate-Setting-for-Azure-App.png" alt="Certificate Setting for Azure App" width="854" height="238" class="aligncenter size-full wp-image-4485" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/Certificate-Setting-for-Azure-App.png?v=1667717853 854w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Certificate-Setting-for-Azure-App-768x214.png?v=1667717853 768w" sizes="(max-width: 854px) 100vw, 854px" /></a><br />
&nbsp;</p>
<p>Next we need to add the <code>Exchange.ManageAsApp</code> API permissions within the app so the application object can access the resource.  To do this we need to add it through the manifest because we won&#8217;t be able to find it via the typical API permissions blade.<br />
&nbsp;</p>
<p>Within the app, navigate to the manifest blade and replace the <code>requiredResourceAccess</code> block with this code. Be sure to click save when it&#8217;s added.</p>
<pre class="brush: powershell; title: ; notranslate">
&quot;requiredResourceAccess&quot;: [
   {
      &quot;resourceAppId&quot;: &quot;00000002-0000-0ff1-ce00-000000000000&quot;,
      &quot;resourceAccess&quot;: [
         {
            &quot;id&quot;: &quot;dc50a0fb-09a3-484d-be87-e023b12c6440&quot;,
            &quot;type&quot;: &quot;Role&quot;
         }
      ]
   }
],
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/App-role-via-App-manifest.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/App-role-via-App-manifest.png" alt="App role via App manifest" width="1473" height="822" class="aligncenter size-full wp-image-4489" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/App-role-via-App-manifest.png?v=1667753748 1473w, https://thesysadminchannel.com/wp-content/uploads/2022/11/App-role-via-App-manifest-1024x571.png?v=1667753748 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/11/App-role-via-App-manifest-768x429.png?v=1667753748 768w" sizes="(max-width: 1473px) 100vw, 1473px" /></a><br />
&nbsp;</p>
<p>Once that is saved, we can verify it was added correctly by going back to API permissions.  We will now see that Exchange.ManageAsApp is the only entry there.<br />
<a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/Admin-consent-to-Exchange-ManageasApp.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/Admin-consent-to-Exchange-ManageasApp.png" alt="Admin consent to Exchange ManageasApp" width="1469" height="650" class="aligncenter size-full wp-image-4492" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/Admin-consent-to-Exchange-ManageasApp.png?v=1667754688 1469w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Admin-consent-to-Exchange-ManageasApp-1024x453.png?v=1667754688 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Admin-consent-to-Exchange-ManageasApp-768x340.png?v=1667754688 768w" sizes="(max-width: 1469px) 100vw, 1469px" /></a><br />
&nbsp;</p>
<p>However, we will notice that the app requires admin consent in order for it to be effective.  Go ahead and consent to it now.  Once complete, it should look like the image below.<br />
<a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/admin-consent-has-been-granted.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/admin-consent-has-been-granted.png" alt="admin consent has been granted" width="1081" height="195" class="aligncenter size-full wp-image-4493" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/admin-consent-has-been-granted.png?v=1667754902 1081w, https://thesysadminchannel.com/wp-content/uploads/2022/11/admin-consent-has-been-granted-1024x185.png?v=1667754902 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/11/admin-consent-has-been-granted-768x139.png?v=1667754902 768w" sizes="(max-width: 1081px) 100vw, 1081px" /></a></p>
<div id="addexchangerole" style="scroll-margin-top: 15px;"></div>
<h2>Add Exchange Administrator Role</h2>
<p>With our app now created and configured properly, we&#8217;ll need to grant the Exchange Administrator role to that app.<br />
&nbsp;</p>
<p>Within Azure AD:</p>
<ul>
<li>Navigate to Roles and administrators</li>
<li>Search for Exchange and click on Exchange administrator</li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/Exchange-Admin-role.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/Exchange-Admin-role.png" alt="Exchange Admin role" width="1459" height="664" class="aligncenter size-full wp-image-4494" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/Exchange-Admin-role.png?v=1667755387 1459w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Exchange-Admin-role-1024x466.png?v=1667755387 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Exchange-Admin-role-768x350.png?v=1667755387 768w" sizes="(max-width: 1459px) 100vw, 1459px" /></a><br />
&nbsp;</p>
<ul>
<li>You should be taken to the <strong>active assignments</strong> for the Exchange admin role</li>
<li>Click on <strong>Add assignments</strong></li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/add-assignments.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/add-assignments.png" alt="add assignments Azure AD role" width="860" height="284" class="aligncenter size-full wp-image-4495" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/add-assignments.png?v=1667755615 860w, https://thesysadminchannel.com/wp-content/uploads/2022/11/add-assignments-768x254.png?v=1667755615 768w" sizes="(max-width: 860px) 100vw, 860px" /></a><br />
&nbsp;</p>
<ul>
<li>Click <strong>no members selected</strong> link</li>
<li>Search for the app name (Our is <strong>Exchange Online Automation</strong>)</li>
<li>Click on the app to add it to the selection</li>
<li>Click select</li>
<li>Complete the prompts to add the role</li>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/Add-Exchange-Role-to-Azure-App-1.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/Add-Exchange-Role-to-Azure-App-1.png" alt="Add Exchange Role to Azure App-1" width="1457" height="803" class="aligncenter size-full wp-image-4500" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/Add-Exchange-Role-to-Azure-App-1.png?v=1667759998 1457w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Add-Exchange-Role-to-Azure-App-1-1024x564.png?v=1667759998 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Add-Exchange-Role-to-Azure-App-1-768x423.png?v=1667759998 768w" sizes="(max-width: 1457px) 100vw, 1457px" /></a><br />
&nbsp;</p>
<p>We should now see our Service Principal listed as an active assignment.<br />
<a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/Exchange-App-added-as-an-active-assignment.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/Exchange-App-added-as-an-active-assignment.png" alt="Exchange App added as an active assignment" width="959" height="326" class="aligncenter size-full wp-image-4497" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/Exchange-App-added-as-an-active-assignment.png?v=1667756322 959w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Exchange-App-added-as-an-active-assignment-768x261.png?v=1667756322 768w" sizes="(max-width: 959px) 100vw, 959px" /></a></p>
<div id="blockquote1">
<strong>Note</strong>: I chose to add this as an active assignment with application permissions because this is intended to be used for unattended automation.
</div>
<div id="connecttoapp" style="scroll-margin-top: 15px;"></div>
<h2>Connect to Exchange Online using the Azure Application</h2>
<p>Finally, we&#8217;re in a spot where we can put all of the pieces together and connect to Exchange Online using our Azure AD application (Service Principal).  Again, since I use PowerShell to manage EXO, we&#8217;re going to connect using the Exchange Online Management module.  Be sure to use the latest version.<br />
&nbsp;</p>
<p>Before we connect, let&#8217;s get the AppId.  We&#8217;ll also need to know the tenant&#8217;s default onmicrosoft name.  To get the AppId, go back to the overview page of the Application we created earlier.<br />
<a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/Get-AppId-for-the-app.jpg" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/Get-AppId-for-the-app.jpg" alt="Get AppId for the app" width="844" height="396" class="aligncenter size-full wp-image-4498" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/Get-AppId-for-the-app.jpg?v=1667757870 844w, https://thesysadminchannel.com/wp-content/uploads/2022/11/Get-AppId-for-the-app-768x360.jpg?v=1667757870 768w" sizes="(max-width: 844px) 100vw, 844px" /></a><br />
&nbsp;</p>
<pre class="brush: powershell; title: ; notranslate">
$AppId = '9e46ef5x-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
$Certificate = Get-ChildItem Cert:\CurrentUser\My\A94FFE108DCxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$TenantName = 'thesysadminchannel.onmicrosoft.com'

Connect-ExchangeOnline -AppId $AppId -Certificate $Certificate -Organization $TenantName -ShowBanner: $false
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/11/exchange-online-certificate-based-authentication.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/11/exchange-online-certificate-based-authentication.png" alt="exchange online certificate based authentication" width="1146" height="457" class="aligncenter size-full wp-image-4499" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/11/exchange-online-certificate-based-authentication.png?v=1667759790 1146w, https://thesysadminchannel.com/wp-content/uploads/2022/11/exchange-online-certificate-based-authentication-1024x408.png?v=1667759790 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/11/exchange-online-certificate-based-authentication-768x306.png?v=1667759790 768w" sizes="(max-width: 1146px) 100vw, 1146px" /></a><br />
&nbsp;</p>
<p>As you can see, we were able to successfully connect to Exchange Online and run the Get-Mailbox command against my account.  As a side note, I&#8217;ve also chosen to not display the banner by using the <code>ShowBanner: $false</code> parameter in the command.</p>
<div id="conclusion" style="scroll-margin-top: 10px;"></div>
<h2>Conclusion</h2>
<p>Hopefully this article on how to use Exchange Online certificate based authentication was insightful and you were able to implement it in your own organization.  This is used pretty much daily to automate tasks in Exchange and it&#8217;s great that we don&#8217;t have to worry about usernames and passwords.<br />
&nbsp;</p>
<p>If you want more information on creating Azure apps and using Graph API, check out my in-depth article on <a href="https://thesysadminchannel.com/how-to-connect-to-microsoft-graph-api-using-powershell/" rel="noopener" target="_blank">how to Connect To Microsoft Graph API Using PowerShell</a>.</p>
<p>The post <a href="https://thesysadminchannel.com/exchange-online-certificate-based-authentication/">Exchange Online Certificate Based Authentication</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/exchange-online-certificate-based-authentication/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4477</post-id>	</item>
		<item>
		<title>Check If An Email Was Read using Graph API PowerShell SDK</title>
		<link>https://thesysadminchannel.com/how-to-check-if-an-email-was-read-using-graph-api-powershell-sdk/</link>
					<comments>https://thesysadminchannel.com/how-to-check-if-an-email-was-read-using-graph-api-powershell-sdk/#comments</comments>
		
		<dc:creator><![CDATA[Paul Contreras]]></dc:creator>
		<pubDate>Fri, 22 Jul 2022 23:36:41 +0000</pubDate>
				<category><![CDATA[Graph API]]></category>
		<category><![CDATA[Powershell]]></category>
		<category><![CDATA[Check If An Email Was Read]]></category>
		<guid isPermaLink="false">https://thesysadminchannel.com/?p=3378</guid>

					<description><![CDATA[<p>With the growing number of people migrating from the Azure AD module to Microsoft Graph API, it&#8217;s great to see some features finally become available via the command line interface .aka. PowerShell. Today we&#8217;ll cover step by step on how&#8230; <a href="https://thesysadminchannel.com/how-to-check-if-an-email-was-read-using-graph-api-powershell-sdk/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a href="https://thesysadminchannel.com/how-to-check-if-an-email-was-read-using-graph-api-powershell-sdk/">Check If An Email Was Read using Graph API PowerShell SDK</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>With the growing number of people migrating from the Azure AD module to Microsoft Graph API, it&#8217;s great to see some features finally become available via the command line interface .aka. PowerShell.  Today we&#8217;ll cover step by step on how to check if an email was read using Graph API PowerShell SDK.<br />
&nbsp;</p>
<div id="tableofcontents">
<h2>Table Of Contents</h2>
<ul>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#getreadstatus">How To Check If An Email Was Read using Graph API PowerShell SDK</a></li>
<ul>
<li><a href="#powershellscript">Get-MSGraphMailReadStatus PowerShell Script</a></li>
<li><a href="#parameters">Script Parameters</a></li>
<li><a href="#examples">Examples and Usage</a></li>
</ul>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<div id="requirements" style="scroll-margin-top: 15px;"></div>
<h2>Requirements</h2>
<p>The use case may vary, sometimes you might want to check how many people read a recent communication that was sent by the organization.  Other times you may need statistics for general read status.  Whatever the case may be, it can be helpful to get this kind of data.<br />
&nbsp;</p>
<p>In order to set this up and check to see if a specific email was read or not, there are certain requirements that need to be put in place in order to successfully query a mailbox.  Let&#8217;s cover those now.<br />
&nbsp;</p>
<ul>
<li>Microsoft.Graph PowerShell Module</li>
<li>EXACT subject line of the email you&#8217;re searching for</li>
<li>An Azure App Registration setup with following API permissions</li>
<ul>
<li>Directory.Read.All</li>
<li>Mail.ReadBasic.All</li>
</ul>
</ul>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/07/Mail-Read-Status-Graph-API-Permissions.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/07/Mail-Read-Status-Graph-API-Permissions.png" alt="Mail Read Status Graph API Permissions" width="1028" height="220" class="aligncenter size-full wp-image-4319" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/07/Mail-Read-Status-Graph-API-Permissions.png?v=1658460616 1028w, https://thesysadminchannel.com/wp-content/uploads/2022/07/Mail-Read-Status-Graph-API-Permissions-1024x219.png?v=1658460616 1024w, https://thesysadminchannel.com/wp-content/uploads/2022/07/Mail-Read-Status-Graph-API-Permissions-768x164.png?v=1658460616 768w" sizes="(max-width: 1028px) 100vw, 1028px" /></a><br />
&nbsp;</p>
<div id="blockquote1">
If you have never created an Azure App or need help getting started with Microsoft Graph API, please follow this guide on <a href="https://thesysadminchannel.com/how-to-connect-to-microsoft-graph-api-using-powershell/" rel="noopener" target="_blank">how to Connect To Microsoft Graph API Using PowerShell</a>.  It will take you from zero knowledge to getting everything up and running in no time.
</div>
<div id="getreadstatus" style="scroll-margin-top: 15px;"></div>
<h2>How To Check If An Email Was Read using Graph API PowerShell SDK</h2>
<p>Now that we have the Azure App Registration and Service Principal created with the above permissions, let&#8217;s look at how we can start getting message read status for our mail items.<br />
&nbsp;</p>
<div id="powershellscript" style="scroll-margin-top: 15px;"></div>
<h2>Get-MSGraphMailReadStatus PowerShell Script</h2>
<p>To give you some insight, this script uses <code>Get-MgUserMessage</code> graph cmdlet under the hood to get the actual message.  There is also a parameter to see which folder the mail item is currently located.  This helps if a user says they&#8217;ve lost an email, when in reality they&#8217;ve accidently dragged it into another folder without realizing it.<br />
&nbsp;</p>
<pre class="brush: powershell; title: ; notranslate">

Function Get-MgMailReadStatus {
&lt;#
.SYNOPSIS
    Get Email read status for users using Graph Api. A session using Connect-Graph must be open as a requirement.

.NOTES
    Name: Get-MgMailReadStatus
    Author: Paul Contreras
    Version: 1.3

.EXAMPLE
    Get-MgMailReadStatus -UserId user1@domain.com, user2@domain.com -Subject 'Exact Subject Line'

.EXAMPLE
    Get-MgMailReadStatus -UserId user1@domain.com -Subject 'Exact Subject Line' -SenderAddress user3@domain.com -StartDate 5/25/2020 -EndDate 10/28/2022 -ShowMailFolder

.PARAMETER UserId
    Checks against this persons mailbox.

.PARAMETER Subject
    Queries the mailbox using the exact text specified

.PARAMETER SenderAddress
    Specify the 'From' Address in your search.  Format should be user@domain.com

.PARAMETER StartDate
    Specify the date when the query should start filtering for.  Format should be MM/DD/YYYY

.PARAMETER EndDate
    Specify the date when the query should end the filter.  Format should be MM/DD/YYYY

.PARAMETER ShowMailFolder
    When this switch is used, it will display what folder the email is currently located in. This makes the overall query slower so use only when needed.

.PARAMETER Top
    Defaulted to 1.  This is the limit of emails per mailbox that you would like to find
#&gt;

    [CmdletBinding()]
    param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 0
        )]
        [Alias('UserPrincipalName')]
        [string[]]  $UserId,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [string]  $Subject,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [string]  $SenderAddress,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [string]  $StartDate,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [string]  $EndDate,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [switch]  $ShowMailFolder,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [int]  $Top = 1,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [switch]    $IncludeRepliesandForwards
    )

    BEGIN {
        $ConnectionGraph = Get-MgContext
        if (-not $ConnectionGraph) {
            Write-Error &quot;Please connect to Microsoft Graph&quot; -ErrorAction Stop
        }

        if ($PSBoundParameters.ContainsKey('StartDate') -and -not $PSBoundParameters.ContainsKey('EndDate')) {
            Write-Error &quot;EndDate is required when a StartDate is entered&quot; -ErrorAction Stop
        }


        if ($PSBoundParameters.ContainsKey('EndDate') -and -not $PSBoundParameters.ContainsKey('StartDate')) {
            Write-Error &quot;StartDate is required when an EndDate is entered&quot; -ErrorAction Stop
        }

        if ($PSBoundParameters.ContainsKey('IncludeRepliesandForwards') -and -not $PSBoundParameters.ContainsKey('Subject')) {
            Write-Error &quot;A Subject is required to use IncludeRepliesandForwards&quot; -ErrorAction Stop
        }

        $Date = Get-Date -Format g
    }

    PROCESS {
        foreach ($User in $UserId) {
            try {
                $GraphUser = Get-MgUser -UserId $User -ErrorAction Stop | select Id, DisplayName, UserPrincipalName
                Write-Verbose &quot;Checking Status for $($GraphUser.DisplayName)&quot;

                #Building the filter query
                if ($PSBoundParameters.ContainsKey('Subject')) {
                    if ($Subject -match &quot;'&quot;) {
                        $Subject = $Subject -replace &quot;'&quot;,&quot;''&quot;
                    }

                    if (-not $PSBoundParameters.ContainsKey('IncludeRepliesandForwards')) {
                        $FilterQuery = &quot;Subject eq '$Subject'&quot;
                    }
                }


                if ($PSBoundParameters.ContainsKey('SenderAddress')) {
                    if ($FilterQuery) {
                        $FilterQuery = $FilterQuery + &quot; AND from/emailAddress/address eq '$SenderAddress'&quot;
                    } else {
                        $FilterQuery = &quot;from/emailAddress/address eq '$SenderAddress'&quot;
                    }
                }


                if ($PSBoundParameters.ContainsKey('StartDate') -and $PSBoundParameters.ContainsKey('EndDate')) {
                    $BeginDateFilter = (Get-Date $StartDate).AddDays(-1).ToString('yyyy-MM-dd') #Adding a day to either side to account for UTC time and MS operators.
                    $EndDateFilter = (Get-Date $EndDate).AddDays(1).ToString('yyyy-MM-dd')

                    if ($FilterQuery) {
                        $FilterQuery = $FilterQuery + &quot; AND ReceivedDateTime ge $BeginDateFilter AND ReceivedDateTime le $EndDateFilter&quot;
                    } else {
                        $FilterQuery = &quot;ReceivedDateTime ge $BeginDateFilter AND ReceivedDateTime le $EndDateFilter&quot;
                    }
                }


                if ($FilterQuery) {
                    if ($Top -gt 10) {
                        $Message = Get-MgUserMessage -UserId $User -Filter $FilterQuery -Top $Top -ErrorAction Stop | Sort-Object ReceivedDateTime -Descending
                      } else {
                        $Message = Get-MgUserMessage -UserId $User -Filter $FilterQuery           -ErrorAction Stop | Sort-Object ReceivedDateTime -Descending | select -First $Top
                    }

                    if ($PSBoundParameters.ContainsKey('IncludeRepliesandForwards')) {
                        $Message = $Message | Where-Object {$_.Subject -like &quot;* $Subject&quot;}
                    }
                } else {
                    $Message = Get-MgUserMessage -UserId $User -ErrorAction Stop | Sort-Object ReceivedDateTime -Descending | select -First $Top
                }


                #Building output object
                $Object = [Ordered]@{
                    RunDate            = $Date
                    MailboxDisplayName = $GraphUser.DisplayName
                    Mailbox            = $GraphUser.UserPrincipalName
                    UserId             = $GraphUser.Id
                    SenderAddress      = $null
                    SenderName         = $null
                    RecipientAddress   = $null
                    RecipientName      = $null
                    Subject            = $null
                    IsRead             = $null
                    HasAttachment      = $null
                    ReceivedDate       = $null
                    MessageId          = $null
                }

                if ($PSBoundParameters.ContainsKey('ShowMailFolder')) {
                    $Object.Add( 'MailFolder', $null )
                    $Object.Add( 'PSTypeName', 'GraphMailReadStatus.MailFolder')
                  } else {
                    $Object.Add( 'PSTypeName', 'GraphMailReadStatus')
                }

                if (-not $Message) {
                    Write-Verbose &quot;0 Messages with the subject: '$Subject' were found on Mailbox: '$($GraphUser.DisplayName)'&quot;

                    #Output object
                    [PSCustomObject]$Object

                } else {
                    Write-Verbose &quot;$($Message.Count) Message(s) with the subject: '$Subject' were returned on Mailbox: '$($GraphUser.DisplayName)'&quot;
                    foreach ($MailItem in $Message) {
                        $Object.SenderAddress      = $MailItem.sender.emailaddress | select -ExpandProperty Address
                        $Object.SenderName         = $MailItem.sender.emailaddress | select -ExpandProperty Name
                        $Object.RecipientAddress   = $MailItem.toRecipients.emailaddress | select -ExpandProperty Address
                        $Object.RecipientName      = $MailItem.toRecipients.emailaddress | select -ExpandProperty Name
                        $Object.Subject            = $MailItem.Subject
                        $Object.IsRead             = $MailItem.IsRead
                        $Object.HasAttachment      = $MailItem.HasAttachments
                        $Object.ReceivedDate       = $MailItem.ReceivedDateTime.ToLocalTime()
                        $Object.MessageId          = $MailItem.Id

                        if ($PSBoundParameters.ContainsKey('ShowMailFolder')) {
                            $MailFolder = Get-MgUserMailFolder -MailFolderId $MailItem.ParentFolderId -UserId $GraphUser.UserPrincipalName | select -ExpandProperty DisplayName

                            $Object.MailFolder = $MailFolder
                            $Object.PSTypeName = 'GraphMailReadStatus.MailFolder'

                          } else {
                            $Object.PSTypeName = 'GraphMailReadStatus'

                        }

                        [PSCustomObject]$Object

                        $Message    = $null
                        $MailItem   = $null
                        $MailFolder = $null
                    }
                }

            } catch {
                Write-Error $_.Exception.Message

            }

            #end foreach block
            $Object = $null
        }
    }

    END {}
}

</pre>
<div id="parameters" style="scroll-margin-top: 15px;"></div>
<h2>Script Parameters</h2>
<h3>    -UserId</h3>
<p>DataType: string/array<br />
Description: Checks against this persons mailbox.  Multiple UPNs/ObjectIds separated by a comma are acceptable.<br />
&nbsp;</p>
<h3>    -Subject</h3>
<p>DataType: string<br />
Description: Queries the mailbox using the EXACT text specified<br />
&nbsp;</p>
<h3>    -SenderAddress</h3>
<p>DataType: string<br />
Description: Specify the &#8216;From&#8217; Address in your search.  Format should be user@domain.com<br />
&nbsp;</p>
<h3>    -StartDate</h3>
<p>DataType: string<br />
Description: Specify the date when the query should start filtering for.  Format should be MM/DD/YYYY<br />
&nbsp;</p>
<h3>    -EndDate</h3>
<p>DataType: string<br />
Description: Specify the date when the query should end the filter.  Format should be MM/DD/YYYY<br />
&nbsp;</p>
<h3>    -ShowMailFolder</h3>
<p>DataType: switch<br />
Description: When this switch is used, it will display what folder the email is currently located in. This makes the overall query slower so use only when needed<br />
&nbsp;</p>
<h3>    -Top</h3>
<p>DataType: int<br />
Description: Defaulted to 1.  This is the limit of emails per mailbox that you would like to find<br />
&nbsp;</p>
<div id="examples" style="scroll-margin-top: 15px;"></div>
<h3>Example 1 &#8211; Specifying a user, a subject and the parent folder</h3>
<pre class="brush: powershell; gutter: false; title: ; notranslate">
PS C:\&gt; Get-MSGraphMailReadStatus -UserId paul@thesysadminchannel.com `
-Subject &quot;You’ve renewed your Office 365 E1 subscription&quot; -ShowMailFolder
</pre>
<p><a href="https://thesysadminchannel.com/wp-content/uploads/2022/07/Get-Mail-Read-Status-Example-1.png" target="_blank" rel="noopener"><img decoding="async" src="https://thesysadminchannel.com/wp-content/uploads/2022/07/Get-Mail-Read-Status-Example-1.png" alt="Get Mail Read Status Example 1" width="845" height="398" class="aligncenter size-full wp-image-4329" srcset="https://thesysadminchannel.com/wp-content/uploads/2022/07/Get-Mail-Read-Status-Example-1.png?v=1658465834 845w, https://thesysadminchannel.com/wp-content/uploads/2022/07/Get-Mail-Read-Status-Example-1-768x362.png?v=1658465834 768w" sizes="(max-width: 845px) 100vw, 845px" /></a></p>
<div id="conclusion" style="scroll-margin-top: 15px;"></div>
<h2>Conclusion</h2>
<p>Hopefully this article was able to show you how to check if an email was read using Graph API PowerShell.  Since this utilizes Graph API, it supports PowerShell 7 and can be incredible fast when using Foreach-Object -Parallel.  I&#8217;ve used this several times in my environment and its great addition to my tool belt.</p>
<p>The post <a href="https://thesysadminchannel.com/how-to-check-if-an-email-was-read-using-graph-api-powershell-sdk/">Check If An Email Was Read using Graph API PowerShell SDK</a> appeared first on <a href="https://thesysadminchannel.com">the Sysadmin Channel</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://thesysadminchannel.com/how-to-check-if-an-email-was-read-using-graph-api-powershell-sdk/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3378</post-id>	</item>
	</channel>
</rss>
