Script to connect to MS Graph without prompting for credentials

Mike G 71 Reputation points
2022-06-03T22:33:44.12+00:00

I am trying to write a script that will connect to MS Graph for the purpose of removing/adding primary users of devices that are enrolled in Intune via Autopilot. My script works fine when I run it manually and now I am trying to find a way to code credentials so I can automate the scripts execution. My problem is I am not sure now to specify the credentials. Full disclosure: I am still learning Powershell and not very savvy so forgive me ahead of time. I found some scripts at https://github.com/microsoftgraph/powershell-intune-samples/tree/master/ManagedDevices for connecting to Graph and modifying the primary user which helped me get started. I took what I needed and combined them with some modifications to get what I needed it to do. Some blogs have pointed me to https://msendpointmgr.com/2017/03/21/getting-started-with-microsoft-intune-and-powershell/ to automate the credentials, but I am not sure if this is the way to go or how to incorporate it though i don't think I need most of it since my script already requests the auth token after entering credentials. I tested the script at https://github.com/MSEndpointMgr/Intune/blob/master/Templates/Script-TemplateWithAuth.ps1 and I do not understand how to code the credentials in it. I have added my script below to help show what I created. If anyone has any input, I would appreciate it.

Thanks

<#

.COPYRIGHT
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
See LICENSE in the project root for license information.

#>

####################################################

param
(
[parameter(Mandatory=$false)]
$DeviceName

)

####################################################

function Get-AuthToken {

<#
.SYNOPSIS
This function is used to authenticate with the Graph API REST interface
.DESCRIPTION
The function authenticate with the Graph API Interface with the tenant name
.EXAMPLE
Get-AuthToken
Authenticates you with the Graph API interface
.NOTES
NAME: Get-AuthToken
#>

[cmdletbinding()]

param
(
    [Parameter(Mandatory=$true)]
    $User
)

$userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User

$tenant = $userUpn.Host

Write-Host "Checking for AzureAD module..."

    $AadModule = Get-Module -Name "AzureAD" -ListAvailable

    if ($AadModule -eq $null) {

        Write-Host "AzureAD PowerShell module not found, looking for AzureADPreview"
        $AadModule = Get-Module -Name "AzureADPreview" -ListAvailable

    }

    if ($AadModule -eq $null) {
        write-host
        write-host "AzureAD Powershell module not installed..." -f Red
        write-host "Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt" -f Yellow
        write-host "Script can't continue..." -f Red
        write-host
        exit
    }

# Getting path to ActiveDirectory Assemblies
# If the module count is greater than 1 find the latest version

    if($AadModule.count -gt 1){

        $Latest_Version = ($AadModule | select version | Sort-Object)[-1]

        $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }

            # Checking if there are multiple versions of the same module found

            if($AadModule.count -gt 1){

            $aadModule = $AadModule | select -Unique

            }

        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"

    }

    else {

        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"

    }

[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null

[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null

$clientId = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"

$redirectUri = "urn:ietf:wg:oauth:2.0:oob"

$resourceAppIdURI = "https://graph.microsoft.com"

$authority = "https://login.microsoftonline.com/$Tenant"

    try {

    $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority

    # https://msdn.microsoft.com/en-us/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx
    # Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession

    $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"

    $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId")

    $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$clientId,$redirectUri,$platformParameters,$userId).Result

        # If the accesstoken is valid then create the authentication header

        if($authResult.AccessToken){

        # Creating header for Authorization token

        $authHeader = @{
            'Content-Type'='application/json'
            'Authorization'="Bearer " + $authResult.AccessToken
            'ExpiresOn'=$authResult.ExpiresOn
            }

        return $authHeader

        }

        else {

        Write-Host
        Write-Host "Authorization Access Token is null, please re-run authentication..." -ForegroundColor Red
        Write-Host
        break

        }

    }

    catch {

    write-host $_.Exception.Message -f Red
    write-host $_.Exception.ItemName -f Red
    write-host
    break

    }

}

####################################################

function Get-Win10IntuneManagedDevices {

<#
.SYNOPSIS
This gets information on Intune managed devices
.DESCRIPTION
This gets information on Intune managed devices
.EXAMPLE
Get-Win10IntuneManagedDevices
.NOTES
NAME: Get-Win10IntuneManagedDevices
#>

[cmdletbinding()]

param
(
[parameter(Mandatory=$false)]
[ValidateNotNullOrEmpty()]
[string]$deviceName
)

    $graphApiVersion = "beta"

    try {

        if($deviceName){

            $Resource = "deviceManagement/managedDevices?`$filter=deviceName eq '$deviceName'"
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" 

            (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).value

        }

        else {

            $Resource = "deviceManagement/managedDevices?`$filter=(((deviceType%20eq%20%27desktop%27)%20or%20(deviceType%20eq%20%27windowsRT%27)%20or%20(deviceType%20eq%20%27winEmbedded%27)%20or%20(deviceType%20eq%20%27surfaceHub%27)))"
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)"

            (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).value

        }

    } catch {
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -f Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        throw "Get-IntuneManagedDevices error"
    }

}

####################################################

function Get-IntuneDevicePrimaryUser {

<#
.SYNOPSIS
This lists the Intune device primary user
.DESCRIPTION
This lists the Intune device primary user
.EXAMPLE
Get-IntuneDevicePrimaryUser
.NOTES
NAME: Get-IntuneDevicePrimaryUser
#>

[cmdletbinding()]

param
(
    [Parameter(Mandatory=$true)]
    [string] $deviceId
)
    $graphApiVersion = "beta"
    $Resource = "deviceManagement/managedDevices"
    $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + "/" + $deviceId + "/users"

    try {

        $primaryUser = Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get

        return $primaryUser.value."id"

    } catch {
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -f Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        throw "Get-IntuneDevicePrimaryUser error"
    }
}

####################################################

function Get-IntuneDevicePrimaryDisplayName {

<#
.SYNOPSIS
This lists the Intune device primary user display name
.DESCRIPTION
This lists the Intune device primary user display name
.EXAMPLE
Get-IntuneDevicePrimaryDisplayName
.NOTES
NAME: Get-IntuneDevicePrimaryDisplayName
#>

[cmdletbinding()]

param
(
    [Parameter(Mandatory=$true)]
    [string] $userId
)
    $graphApiVersion = "beta"
    $Resource = "users"
    $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + "/" + $userId

    try {

        $primaryUserName = Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get

        #return $primaryUserName.value."displayName"
        return $primaryUserName

    } catch {
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -f Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        throw "Get-IntuneDevicePrimaryUser error"
    }
}

####################################################

function Delete-IntuneDevicePrimaryUser {

<#
.SYNOPSIS
This deletes the Intune device primary user
.DESCRIPTION
This deletes the Intune device primary user
.EXAMPLE
Delete-IntuneDevicePrimaryUser
.NOTES
NAME: Delete-IntuneDevicePrimaryUser
#>

[cmdletbinding()]

param
(
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$IntuneDeviceId
)

    $graphApiVersion = "beta"
    $Resource = "deviceManagement/managedDevices('$IntuneDeviceId')/users/`$ref"

    try {

        $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)"

        Invoke-RestMethod -Uri $uri -Headers $authToken -Method Delete

    }

    catch {

        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -f Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        throw "Delete-IntuneDevicePrimaryUser error"

    }

}

####################################################

#region Authentication

write-host

# Checking if authToken exists before running authentication
if($global:authToken){

    # Setting DateTime to Universal time to work in all timezones
    $DateTime = (Get-Date).ToUniversalTime()

    # If the authToken exists checking when it expires
    $TokenExpires = ($authToken.ExpiresOn.datetime - $DateTime).Minutes

    if($TokenExpires -le 0){

        write-host "Authentication Token expired" $TokenExpires "minutes ago" -ForegroundColor Yellow
        write-host

        # Defining User Principal Name if not present

        if($User -eq $null -or $User -eq ""){
            #$User = Read-Host -Prompt "Please specify your user principal name for Azure Authentication"
            $User = "user@domain.com"
            Write-Host
        }

        $global:authToken = Get-AuthToken -User $User
    }
}

# Authentication doesn't exist, calling Get-AuthToken function

else {

    if($User -eq $null -or $User -eq "") {
        #$User = Read-Host -Prompt "Please specify your user principal name for Azure Authentication"
        $User = "user@domain.com"
        Write-Host
    }

    # Getting the authorization token
    $global:authToken = Get-AuthToken -User $User
}

#endregion

####################################################

$TimeDate = Get-Date -format MMddyy-hhmmsstt
$TranscriptPath = "\\domain.com\projects\Autopilot\Logs\ImagedPCNames_$TimeDate.log"
$PCFilename = Get-ChildItem -Path "\\domain.com\projects\Autopilot\ImagedPCNames.txt"
$ArchiveSource = "\\domain.com\projects\Autopilot\ImagedPCNames*_COMPLETE.txt"
$ArchiveDest = "\\domain.com\projects\Autopilot\Archive"
$PC_list = Get-Content -Path "\\domain.com\projects\Autopilot\ImagedPCNames.txt"

Start-Transcript -Path $TranscriptPath

foreach ($Device in $PC_List) {

$TargetDevice = Get-Win10IntuneManagedDevices -deviceName "$Device"
#echo $TargetDevice

if($Device){

    Write-Host
    Write-Host "Device name:" $Device -ForegroundColor Cyan

    $IntuneDevicePrimaryUser = Get-IntuneDevicePrimaryUser -deviceId $TargetDevice.id
    if($null -ne $IntuneDevicePrimaryUser){
        $IntuneDevicePrimaryUserDisplay = Get-IntuneDevicePrimaryDisplayName -userId $IntuneDevicePrimaryUser


        Write-Host "Intune Device Primary UserID:"  $IntuneDevicePrimaryUser
        Write-Host "Intune Device Primary User:" $IntuneDevicePrimaryUserDisplay.displayname

        $DeleteIntuneDevicePrimaryUser = Delete-IntuneDevicePrimaryUser -IntuneDeviceId $TargetDevice.id

        if($DeleteIntuneDevicePrimaryUser -eq ""){

            Write-Host "User deleted as Primary User from the device '$Device'..." -ForegroundColor Green


            }
        }
    }
    else 
    {
        Write-Host "No Primary User on $Device..."
    }
}

Rename-Item $PCFilename.FullName -newname ($PCFilename.Basename + $TimeDate + "_COMPLETE" + $PCFilename.Extension)
Start-Sleep -Seconds 2
Move-Item -Path $ArchiveSource -Destination $ArchiveDest
Stop-Transcript
Windows Autopilot
Windows Autopilot
A collection of Microsoft technologies used to set up and pre-configure new devices and to reset, repurpose, and recover devices.
407 questions
Windows Server PowerShell
Windows Server PowerShell
Windows Server: A family of Microsoft server operating systems that support enterprise-level management, data storage, applications, and communications.PowerShell: A family of Microsoft task automation and configuration management frameworks consisting of a command-line shell and associated scripting language.
5,363 questions
Microsoft Intune
Microsoft Intune
A Microsoft cloud-based management solution that offers mobile device management, mobile application management, and PC management capabilities.
4,333 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. DaveK 1,846 Reputation points
    2022-06-09T12:49:10.157+00:00

    I've just started to play with some graph automation relating to Mobile devices so I've seen some of the examples you've linked to already and I ended up writing a smaller script to get my started. I decided to go down the route of using a App Registration, creating a Client Secret and applying the required API permissions for graph. Then I can use that info to connect using the following.

    # -------- API Registation Details --------   
    $ClientID = "[YOUR_CLIENTID]"  
    $TenantID = "[YOUR_TenantID]"  
    $ClientSecret = "[YOUR_SECRET]"  
      
    # -------- Grab Auth Token --------   
    $uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"  
    $body = @{  
    	client_id     = $clientId  
    	scope         = "https://graph.microsoft.com/.default"  
    	client_secret = $clientSecret  
    	grant_type    = "client_credentials"  
    }  
    $tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing  
    $access_token = ($tokenRequest.Content | ConvertFrom-Json).access_token  
    $token_type = ($tokenRequest.Content | ConvertFrom-Json).token_type  
      
      
    # -------- Make Graph request --------   
    $Header = @{Authorization = "$($token_type) $($access_token)"}  
    $MobileApps = (Invoke-RestMethod -Headers $Header -Uri "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps" -Method Get -ContentType "application/json").Value  
    

    The example point to the mobiles URI but the way it works should be the same for any endpoint I believe.

    The way I've used this is to run as a scheduled task running as a local account, then used that local account to encrypt the client secret so it can only be read by that account on the machine it was encrypted on. Probably not 100% bomb proof but better than plain text secrets in scripts.

    0 comments No comments