PSIntuneAuth.psm1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
function Get-MSIntuneAuthToken {
    <#
    .SYNOPSIS
        Get an authentication token required for interacting with Microsoft Intune using Microsoft Graph API
        NOTE: This function requires that AzureAD module is installed. Use 'Install-Module -Name AzureAD' to install it.
 
    .PARAMETER TenantName
        A tenant name should be provided in the following format: tenantname.onmicrosoft.com.
 
    .PARAMETER ClientID
        Application ID for an Azure AD application. Uses by default the Microsoft Intune PowerShell application ID.
 
    .PARAMETER ClientSecret
        Web application client secret.
 
    .PARAMETER Credential
        Specify a PSCredential object containing username and password.
 
    .PARAMETER Resource
        Resource recipient (app, e.g. Graph API). Leave empty to use https://graph.microsoft.com as default.
 
    .PARAMETER RedirectUri
        Redirect URI for Azure AD application. Leave empty to leverage Azure PowerShell well known redirect URI.
 
    .PARAMETER PromptBehavior
        Set the prompt behavior when acquiring a token.
 
    .EXAMPLE
        # Manually specify username and password to acquire an authentication token:
        Get-MSIntuneAuthToken -TenantName domain.onmicrsoft.com
 
        # Manually specify username and password to acquire an authentication token using a specific client ID:
        Get-MSIntuneAuthToken -TenantName domain.onmicrsoft.com -ClientID "<GUID>"
 
        # Retrieve a PSCredential object with username and password to acquire an authentication token:
        $Credential = Get-Credential
        Get-MSIntuneAuthToken -TenantName domain.onmicrsoft.com -Credential $Credential
 
        # Retrieve a PSCredential object for usage with Azure Automation containing the username and password to acquire an authentication token:
        $Credential = Get-AutomationPSCredential -Name "<CredentialName>"
        Get-MSIntuneAuthToken -TenantName domain.onmicrsoft.com -ClientID "<GUID>" -Credential $Credential
 
    .NOTES
        Author: Nickolaj Andersen
        Contact: @NickolajA
        Created: 2017-09-27
        Updated: 2021-01-12
 
        Version history:
        1.0.0 - (2017-09-27) Function created
        1.0.1 - (2017-10-08) Added ExpiresOn property
        1.0.2 - (2018-01-22) Added support for specifying PSCredential object for silently retrieving an authentication token without being prompted
        1.0.3 - (2018-01-22) Fixed an issue with prompt behavior parameter not being used
        1.0.4 - (2018-01-22) Fixed an issue when detecting the AzureAD module presence
        1.0.5 - (2018-01-22) Enhanced the AzureAD module detection logic
        1.0.6 - (2018-01-28) Changed so that the Microsoft Intune PowerShell application ID is set as default for ClientID parameter
        1.2.0 - (2019-10-27) Added support for using app-only authentication using a client ID and client secret for a web app. Resource recipient is now also possible
                             to specify directly on the command line instead of being hard-coded. Now using the latest authority URI and installs the AzureAD module automatically.
        1.2.1 - (2020-01-15) Fixed an issue where when multiple versions of the AzureAD module installed would cause an error attempting in re-installing the Azure AD module
        1.2.2 - (2020-01-28) Added more verbose logging output for further troubleshooting in case an auth token is not aquired
        1.2.3 - (2021-01-12) Added support for installing the AzureAD module along side with the AzureADPreview module
    #>

    [CmdletBinding()]
    param(
        [parameter(Mandatory=$true, ParameterSetName="AuthPrompt", HelpMessage="A tenant name should be provided in the following format: tenantname.onmicrosoft.com.")]
        [parameter(Mandatory=$true, ParameterSetName="AuthCredential")]
        [parameter(Mandatory=$false, ParameterSetName="AuthAppOnly")]
        [ValidateNotNullOrEmpty()]
        [string]$TenantName,

        [parameter(Mandatory=$false, ParameterSetName="AuthPrompt", HelpMessage="Application ID for an Azure AD application. Uses by default the Microsoft Intune PowerShell application ID.")]
        [parameter(Mandatory=$false, ParameterSetName="AuthCredential")]
        [parameter(Mandatory=$false, ParameterSetName="AuthAppOnly")]
        [ValidateNotNullOrEmpty()]
        [string]$ClientID = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547",

        [parameter(Mandatory=$true, ParameterSetName="AuthAppOnly", HelpMessage="Web application client secret.")]
        [ValidateNotNullOrEmpty()]
        [string]$ClientSecret,

        [parameter(Mandatory=$true, ParameterSetName="AuthCredential", HelpMessage="Specify a PSCredential object containing username and password.")]
        [ValidateNotNullOrEmpty()]
        [PSCredential]$Credential,

        [parameter(Mandatory=$false, ParameterSetName="AuthPrompt", HelpMessage="Resource recipient (app, e.g. Graph API). Leave empty to use https://graph.microsoft.com as default.")]
        [parameter(Mandatory=$false, ParameterSetName="AuthCredential")]
        [parameter(Mandatory=$false, ParameterSetName="AuthAppOnly")]
        [ValidateNotNullOrEmpty()]
        [string]$Resource = "https://graph.microsoft.com",

        [parameter(Mandatory=$false, ParameterSetName="AuthPrompt", HelpMessage="Redirect URI for Azure AD application. Leave empty to leverage Azure PowerShell well known redirect URI.")]
        [parameter(Mandatory=$false, ParameterSetName="AuthCredential")]
        [ValidateNotNullOrEmpty()]
        [string]$RedirectUri = "urn:ietf:wg:oauth:2.0:oob",

        [parameter(Mandatory=$false, ParameterSetName="AuthPrompt", HelpMessage="Set the prompt behavior when acquiring a token.")]
        [parameter(Mandatory=$false, ParameterSetName="AuthCredential")]
        [ValidateNotNullOrEmpty()]
        [ValidateSet("Auto", "Always", "Never", "RefreshSession")]
        [string]$PromptBehavior = "Auto"
    )
    Process {
        $ErrorActionPreference = "Stop"

        # Determine if the AzureAD module needs to be installed or updated to latest version
        try {
            Write-Verbose -Message "Attempting to locate AzureAD module on local system"
            $AzureADModule = Get-Module -Name "AzureAD" -ListAvailable -Verbose:$false
            if ($AzureADModule -ne $null) {
                if (($AzureADModule | Measure-Object).Count -eq 1) {
                    $CurrentModuleVersion = Get-Module -Name "AzureAD" -ListAvailable -ErrorAction Stop -Verbose:$false | Select-Object -ExpandProperty Version
                }
                else {
                    $CurrentModuleVersion = Get-Module -Name "AzureAD" -ListAvailable -ErrorAction Stop -Verbose:$false | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version
                }
                $LatestModuleVersion = (Find-Module -Name "AzureAD" -ErrorAction Stop -Verbose:$false).Version
                Write-Verbose -Message "AzureAD module detected, checking for latest version"
                if ($LatestModuleVersion -gt $CurrentModuleVersion) {
                    Write-Verbose -Message "Latest version of AzureAD module is not installed, attempting to install: $($LatestModuleVersion.ToString())"
                    $UpdateModuleInvocation = Update-Module -Name "AzureAD" -Scope "AllUsers" -Force -ErrorAction Stop -Confirm:$false -Verbose:$false
                }
                else {
                    Write-Verbose -Message "Latest version for AzureAD module was detected, continue to aquire authentication token"
                }
            }
            else {
                throw "Unable to detect Azure AD module"
            }
        }
        catch [System.Exception] {
            Write-Warning -Message "Unable to detect AzureAD module, attempting to install from online repository"
            try {
                # Install NuGet package provider
                $PackageProvider = Install-PackageProvider -Name NuGet -Force -Verbose:$false

                # Install AzureAD module
                $InstallArgs = @{
                    "Name" = "AzureAD"
                    "Scope" = "AllUsers"
                    "Force" = $true
                    "ErrorAction" = "Stop"
                    "Confirm" = $false
                    "Verbose" = $false
                }

                # Amend install args if AzureADPreview module is detected
                $AzureADPreviewModule = Get-Module -Name "AzureADPreview" -ListAvailable -Verbose:$false
                if ($AzureADPreviewModule -ne $null) {
                    Write-Verbose -Message "Detected that the AzureADPreview module was installed, adding 'AllowClobber' parameter"
                    $InstallArgs.Add("AllowClobber", $true)
                }

                Install-Module @InstallArgs
                Write-Verbose -Message "Successfully installed AzureAD"
            }
            catch [System.Exception] {
                Write-Warning -Message "An error occurred while attempting to install AzureAD module. Error message: $($_.Exception.Message)"; break
            }
        }

        try {
            # Get installed Azure AD module
            $AzureADModules = Get-Module -Name "AzureAD" -ListAvailable -ErrorAction Stop -Verbose:$false

            if ($AzureADModules -ne $null) {
                # Check if multiple modules exist and determine the module path for the most current version
                if (($AzureADModules | Measure-Object).Count -gt 1) {
                    $LatestAzureADModule = ($AzureADModules | Select-Object -Property Version | Sort-Object)[-1]
                    $AzureADModulePath = $AzureADModules | Where-Object { $_.Version -like $LatestAzureADModule.Version } | Select-Object -ExpandProperty ModuleBase
                }
                else {
                    $AzureADModulePath = $AzureADModules | Select-Object -ExpandProperty ModuleBase
                }

                try {
                    # Construct array for required assemblies from Azure AD module
                    $Assemblies = @(
                        (Join-Path -Path $AzureADModulePath -ChildPath "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"),
                        (Join-Path -Path $AzureADModulePath -ChildPath "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll")
                    )

                    # Load required assemblies
                    Add-Type -Path $Assemblies -ErrorAction Stop -Verbose:$false

                    try {
                        # Construct variable for authority URI
                        switch ($PSCmdlet.ParameterSetName) {
                            "AuthAppOnly" {
                                $Authority = "https://login.microsoftonline.com/$($TenantName)"
                            }
                            default {
                                $Authority = "https://login.microsoftonline.com/$($TenantName)/oauth2/v2.0/token"       
                            }
                        }

                        # Construct new authentication context
                        $AuthenticationContext = New-Object -TypeName "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $Authority -ErrorAction Stop

                        # Construct platform parameters
                        $PlatformParams = New-Object -TypeName "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList $PromptBehavior -ErrorAction Stop

                        try {
                            # Determine parameters when acquiring token
                            Write-Verbose -Message "Currently running in parameter set context: $($PSCmdlet.ParameterSetName)"
                            switch ($PSCmdlet.ParameterSetName) {
                                "AuthPrompt" {
                                    # Acquire access token
                                    Write-Verbose -Message "Attempting to acquire access token using user delegation"
                                    $AuthenticationResult = ($AuthenticationContext.AcquireTokenAsync($Resource, $ClientID, $RedirectUri, $PlatformParams)).Result
                                }
                                "AuthCredential" {
                                    # Construct required identity model user password credential
                                    Write-Verbose -Message "Attempting to acquire access token using legacy user delegation with username and password"
                                    $UserPasswordCredential = New-Object -TypeName "Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential" -ArgumentList ($Credential.UserName, $Credential.Password) -ErrorAction Stop
            
                                    # Acquire access token
                                    $AuthenticationResult = ([Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions]::AcquireTokenAsync($AuthenticationContext, $Resource, $ClientID, $UserPasswordCredential)).Result
                                }
                                "AuthAppOnly" {
                                    # Construct required identity model client credential
                                    Write-Verbose -Message "Attempting to acquire access token using app-based authentication"
                                    $ClientCredential = New-Object -TypeName "Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential" -ArgumentList ($ClientID, $ClientSecret) -ErrorAction Stop

                                    # Acquire access token
                                    $AuthenticationResult = ($AuthenticationContext.AcquireTokenAsync($Resource, $ClientCredential)).Result
                                }
                            }
                            
                            # Check if access token was acquired
                            if ($AuthenticationResult.AccessToken -ne $null) {
                                Write-Verbose -Message "Successfully acquired an access token for authentication"

                                # Construct authentication hash table for holding access token and header information
                                $Authentication = @{
                                    "Content-Type" = "application/json"
                                    "Authorization" = -join("Bearer ", $AuthenticationResult.AccessToken)
                                    "ExpiresOn" = $AuthenticationResult.ExpiresOn
                                }
            
                                # Return the authentication token
                                return $Authentication                    
                            }
                            else {
                                Write-Warning -Message "Failure to acquire access token. Response with access token was null"; break
                            }
                        }
                        catch [System.Exception] {
                            Write-Warning -Message "An error occurred when attempting to call AcquireTokenAsync method. Error message: $($_.Exception.Message)"; break
                        }
                    }
                    catch [System.Exception] {
                        Write-Warning -Message "An error occurred when constructing an authentication token. Error message: $($_.Exception.Message)"; break
                    }
                }
                catch [System.Exception] {
                    Write-Warning -Message "Unable to load required assemblies from AzureAD module to construct an authentication token. Error message: $($_.Exception.Message)"; break
                }
            }
            else {
                Write-Warning -Message "Azure AD PowerShell module is not present on this system, please install before you continue"; break
            }
        }
        catch [System.Exception] {
            Write-Warning -Message "Unable to load required AzureAD module to for retrieving an authentication token. Error message: $($_.Exception.Message)"; break
        }
    }
}

function Set-MSIntuneAdminConsent {
    <#
    .SYNOPSIS
        Grant admin consent for delegated admin permissions.
        NOTE: This function requires that AzureAD module is installed. Use 'Install-Module -Name AzureAD' to install it.
 
    .PARAMETER TenantName
        A tenant name should be provided in the following format: tenantname.onmicrosoft.com.
 
    .PARAMETER ClientID
        Specify a Global Admin user principal name.
 
    .EXAMPLE
        # Grant admin consent for delegated admin permissions for an Intune tenant:
        Set-MSIntuneAdminConsent -TenantName domain.onmicrsoft.com -UserPrincipalName "globaladmin@domain.onmicrosoft.com"
 
    .NOTES
    Author: Nickolaj Andersen
    Contact: @NickolajA
    Created: 2018-01-28
    Updated: 2018-01-28
 
    Version history:
    1.0.0 - (2018-01-28) Function created
    1.0.1 - (2018-01-28) Added static prompt behavior parameter with value of Auto
 
    #>

    [CmdletBinding()]
    param(
        [parameter(Mandatory=$true, HelpMessage="A tenant name should be provided in the following format: tenantname.onmicrosoft.com.")]
        [ValidateNotNullOrEmpty()]
        [string]$TenantName,

        [parameter(Mandatory=$true, HelpMessage="Specify a Global Admin user principal name.")]
        [ValidateNotNullOrEmpty()]
        [string]$UserPrincipalName
    )

    try {
        # Get installed Azure AD modules
        $AzureADModules = Get-Module -Name "AzureAD" -ListAvailable -ErrorAction Stop -Verbose:$false

        if ($AzureADModules -ne $null) {
            # Check if multiple modules exist and determine the module path for the most current version
            if (($AzureADModules | Measure-Object).Count -gt 1) {
                $LatestAzureADModule = ($AzureADModules | Select-Object -Property Version | Sort-Object)[-1]
                $AzureADModulePath = $AzureADModules | Where-Object { $_.Version -like $LatestAzureADModule.Version } | Select-Object -ExpandProperty ModuleBase
            }
            else {
                $AzureADModulePath = Get-Module -Name "AzureAD" -ListAvailable -ErrorAction Stop -Verbose:$false | Select-Object -ExpandProperty ModuleBase
            }

            # Construct array for required assemblies from Azure AD module
            $Assemblies = @(
                (Join-Path -Path $AzureADModulePath -ChildPath "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"),
                (Join-Path -Path $AzureADModulePath -ChildPath "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll")
            )
            Add-Type -Path $Assemblies -ErrorAction Stop

            try {
                # Set static variables
                $Authority = "https://login.microsoftonline.com/$($TenantName)/oauth2/token"
                $ResourceRecipient = "https://graph.microsoft.com"
                $RedirectUri = "urn:ietf:wg:oauth:2.0:oob"
                $ClientID = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547" # Default Microsoft Intune PowerShell enterprise application

                # Construct new authentication context
                $AuthenticationContext = New-Object -TypeName "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $Authority -ErrorAction Stop

                # Construct platform parameters
                $PlatformParams = New-Object -TypeName "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto" -ErrorAction Stop

                # Construct user identifier
                $UserIdentifier = New-Object -TypeName "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($UserPrincipalName, "OptionalDisplayableId")

                # Acquire authentication token and invoke admin consent
                $AuthenticationResult = ($AuthenticationContext.AcquireTokenAsync($ResourceRecipient, $ClientID, $RedirectUri, $PlatformParams, $UserIdentifier, "prompt=admin_consent")).Result
               
                # Check if access token was acquired
                if ($AuthenticationResult.AccessToken -ne $null) {
                    # Construct authentication hash table for holding access token and header information
                    $Authentication = @{
                        "Content-Type" = "application/json"
                        "Authorization" = -join("Bearer ", $AuthenticationResult.AccessToken)
                        "ExpiresOn" = $AuthenticationResult.ExpiresOn
                    }

                    # Return the authentication token
                    return $Authentication                    
                }
                else {
                    Write-Warning -Message "Failure to acquire access token. Response with access token was null" ; break
                }
            }
            catch [System.Exception] {
                Write-Warning -Message "An error occurred when constructing an authentication token: $($_.Exception.Message)" ; break
            }
        }
        else {
            Write-Warning -Message "Azure AD PowerShell module is not present on this system, please install before you continue" ; break
        }
    }
    catch [System.Exception] {
        Write-Warning -Message "Unable to load required assemblies (Azure AD PowerShell module) to construct an authentication token. Error: $($_.Exception.Message)" ; break
    }    
}