#requires -version 3
<#
.SYNOPSIS
.
.DESCRIPTION
.
.NOTES
Version: 1.0
Author: Alex Ø. T. Hansen (ath@tofte-it.dk)
Creation Date: 01-12-2018
Purpose/Change: Initial script development
#>
#region begin boostrap
############### Bootstrap - Start ###############
#Clear the screen.
Clear-Host;
#Assemblies.
Add-Type -AssemblyName System.Web;
############### Bootstrap - End ###############
#endregion
#region begin input
############### Input - Start ###############
#Folders:
$Folders = @{
Logs = ("C:\Scripts\ProfilePicture\Logs\");
Data = ("C:\Scripts\ProfilePicture\Data\");
};
#Files:
$Files = @{
Script = ("C:\Scripts\ProfilePicture\Set-AzureADProfilePicture.ps1");
};
#Azure AD Application.
$OauthTokenEndpoint = "https://login.microsoftonline.com/<GUID>/oauth2/token";
$ClientID = "<Client ID>";
$ClientSecret = "<Client Secret>";
############### Input - End ###############
#endregion
#region begin functions
############### Functions - Start ###############
#Write to the console.
Function Write-Console
{
[cmdletbinding()]
Param
(
[Parameter(Mandatory=$false)][string]$Category,
[Parameter(Mandatory=$false)][string]$Text
)
#If the input is empty.
If([string]::IsNullOrEmpty($Text))
{
$Text = " ";
}
#If category is not present.
If([string]::IsNullOrEmpty($Category))
{
#Write to the console.
Write-Output("[" + (Get-Date).ToString("dd/MM-yyyy HH:mm:ss") + "]: " + $Text + ".");
}
Else
{
#Write to the console.
Write-Output("[" + (Get-Date).ToString("dd/MM-yyyy HH:mm:ss") + "][" + $Category + "]: " + $Text + ".");
}
}
#Sets a user profile picture.
Function Set-UserProfilePicture
{
[cmdletbinding()]
Param
(
[Parameter(Mandatory=$true)]$SID,
[Parameter(Mandatory=$true)]$FilePath
)
#Check if the profile picture exist.
If(Test-Path -Path $FilePath)
{
#Create new image folder.
$ImageBase = ($env:public + "\AccountPictures\" + $SID);
$ImageBaseFolder = (New-Item -Path $ImageBase -ItemType Directory -Force) | Out-Null;
#Create registry path.
$RegistryPath = ("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AccountPicture\Users\" + $SID);
New-Item -Path $RegistryPath -Force | Out-Null;
#Array of image sizes.
$ImageSizes = @(32, 40, 48, 96, 192, 200, 240, 448);
#Foreach image size.
Foreach($ImageSize in $ImageSizes)
{
#Set photo filename.
$PhotoFileName = ("Image" + $ImageSize + ".jpg");
#Save the photo.
Copy-Item -Path $FilePath -Destination ($ImageBase + "\" + $PhotoFileName) -Force;
#Create new registry key.
New-ItemProperty -Path $RegistryPath -Name ("Image" + $ImageSize) -Value ($ImageBase + "\" + $PhotoFileName) -Force | Out-Null;
}
}
}
#Get cache user information from the registry.
Function Get-CacheUserInformation
{
#Array to store user information.
$Users = @();
#Get all identities except system users.
$Identities = Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\IdentityStore\Cache" | Where-Object {$_.Name -notlike "*S-1-5*"};
#Foreach identity found.
Foreach($Identity in $Identities)
{
#Clear variables.
$UPN = $null;
$SID = $null;
$SamAccountName = $null;
$Domain = $null;
#Set SID.
$SID = $Identity.PSChildName;
#Get key values.
$KeyValues = Get-ItemProperty -Path ("HKLM:\SOFTWARE\Microsoft\IdentityStore\Cache\" + $SID + "\IdentityCache\" + $SID);
#Set variables.
$UPN = $KeyValues.UserName;
$SamAccountName = $KeyValues.SAMName;
$Domain = $KeyValues.ProviderName;
#Create a new object.
$User = New-Object -TypeName psobject;
#Add data to the object.
Add-Member -InputObject $User -MemberType NoteProperty -Name "UPN" -Value $UPN;
Add-Member -InputObject $User -MemberType NoteProperty -Name "SID" -Value $SID;
Add-Member -InputObject $User -MemberType NoteProperty -Name "SamAccountName" -Value $SamAccountName;
Add-Member -InputObject $User -MemberType NoteProperty -Name "Domain" -Value $Domain;
#Add object to array.
$Users += $User;
}
#Return object array.
Return $Users;
}
#Get access token from Graph API.
Function Get-GraphAccessToken
{
[cmdletbinding()]
Param
(
[Parameter(Mandatory=$true)][string]$TokenEndpoint,
[Parameter(Mandatory=$true)][string]$ClientID,
[Parameter(Mandatory=$true)][string]$ClientSecret
)
#Encode the client secret so it removes any special characters.
$ClientSecretEncoded = [System.Web.HttpUtility]::UrlEncode($ClientSecret);
#Construct request body to Graph API.
$AuthBody = ("grant_type=client_credentials" + "&client_id=$ClientID" + "&client_secret=$ClientSecretEncoded" + "&resource=https://graph.microsoft.com/");
#Call the Graph API to get bearer token.
$AuthReponse = Invoke-RestMethod -Method Post -Uri $OauthTokenEndpoint -body $AuthBody -ContentType "application/x-www-form-urlencoded";
#Return access token.
Return ($AuthReponse.access_token);
}
#Get the user profile picture from Azure AD.
Function Get-UserProfilePicture
{
[cmdletbinding()]
Param
(
[Parameter(Mandatory=$true)][string]$AccessToken,
[Parameter(Mandatory=$true)][string]$UPN,
[Parameter(Mandatory=$true)][string]$Destination
)
#Invoke rest method to Graph API with access token.
Invoke-RestMethod -Method Get -Uri ("https://graph.microsoft.com/v1.0/users/" + $UPN + '/photo/$value') -Headers @{"Authorization"="Bearer $($AccessToken)"} -OutFile $Destination;
}
#Create a schedule task.
Function New-ProfilePictureScheduleTask
{
[cmdletbinding()]
Param
(
[Parameter(Mandatory=$true)][string]$Script
)
#Template to create the schedule task.
$XML = @"
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2018-12-01T00:00:00.0000000</Date>
<Author>blog.tofte-it.dk</Author>
<URI>\Set Profile Account Pictures</URI>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<StartBoundary>2018-12-01T10:00:00+01:00</StartBoundary>
<ExecutionTimeLimit>P1D</ExecutionTimeLimit>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>true</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>true</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>P1D</ExecutionTimeLimit>
<Priority>7</Priority>
<RestartOnFailure>
<Interval>PT15M</Interval>
<Count>3</Count>
</RestartOnFailure>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
<Arguments>-ExecutionPolicy Bypass -File "$($Script)"</Arguments>
</Exec>
</Actions>
</Task>
"@;
#Add schedule task.
Register-ScheduledTask -Xml $XML -TaskName "Set Profile Account Pictures" | Out-Null;
}
#Check if the schedule task already exist.
Function Test-ScheduleTask
{
[CmdletBinding()]
Param
(
[parameter(Mandatory=$true)][string]$Name
)
#Create a new schedule object.
$Schedule = New-Object -com Schedule.Service;
#Connect to the store.
$Schedule.Connect();
#Get schedule tak folders.
$Task = $Schedule.GetFolder("\").GetTasks(0) | Where-Object {$_.Name -eq $Name -and $_.Enabled -eq $true};
#If the task exists and is enabled.
If($Task)
{
#Return true.
Return $true;
}
#If the task doesn't exist.
Else
{
#Return false.
Return $false;
}
}
############### Functions - End ###############
#endregion
#region begin main
############### Main - Start ###############
#Create folders.
New-Item -Path $Folders.Logs -ItemType Directory -Force | Out-Null;
New-Item -Path $Folders.Data -ItemType Directory -Force | Out-Null;
#Start transcript.
Start-Transcript -Path ($Folders.Logs + "\" + (Get-Date).ToString("ddMMyyyy") + ".log") -Append -Force;
#Get access token to access Graph API.
Write-Console -Category "Access Token" -Text "Getting Graph API access token";
$AccessToken = Get-GraphAccessToken -TokenEndpoint $OauthTokenEndpoint -ClientID $ClientID -ClientSecret $ClientSecret;
#Get all cached users.
Write-Console -Category "Registry" -Text "Getting cached users from registry";
$Users = Get-CacheUserInformation;
#Foreach user cached.
Write-Console -Category "Users" -Text "Enumerating cached users";
Foreach($User in $Users)
{
#Write to log.
Write-Output "";
Write-Console -Category $User.UPN -Text $User.UPN;
Write-Console -Category $User.UPN -Text $User.SID;
Write-Console -Category $User.UPN -Text $User.SamAccountName;
Write-Console -Category $User.UPN -Text $User.Domain;
#Download the profile picture.
Write-Console -Category $User.UPN -Text ("Downloading profile picture to '" + ($Folders.Data + $User.UPN + ".jpeg") + "'");
Get-UserProfilePicture -AccessToken $AccessToken -UPN $User.UPN -Destination ($Folders.Data + $User.UPN + ".jpeg");
#Set the user profile picture.
Write-Console -Category $User.UPN -Text ("Setting registry keys");
Set-UserProfilePicture -SID $User.SID -FilePath ($Folders.Data + $User.UPN + ".jpeg");
}
#Output empty line.
Write-Output "";
#Check if the schedule task doesn't exist.
If(!(Test-ScheduleTask -Name "Set Profile Account Pictures"))
{
#Create the schedule task.
Write-Console -Category "Schedule Task" -Text ("Creating schedule task");
New-ProfilePictureScheduleTask -Script $Files.Script;
#Copy running script.
Write-Console -Category "Schedule Task" -Text ("Copying running script to '" + $Files.Script + "'");
Copy-Item -Path $PSCommandPath -Destination $Files.Script;
}
############### Main - End ###############
#endregion
#region begin finalize
############### Finalize - Start ###############
#Stop transcript.
Stop-Transcript;
############### Finalize - End ###############
#endregion