So, it’s time. It’s with mixed feeling that I write (and publish) this post, but I feel that it’s necessary for me to let you have a look and gather your feedback. So, please, feel free to reach out to me with your comments, thoughts and advices. I’m publishing the code and my thoughts in a 0.5 version. I’m still waiting for some feedback from friends and colleagues, its not complete yet but I do think that you’ll get the point of the script and the logic behind it. Further down ill have a short to-do list with all the things I would like to include before considering this 1.0. However, feel free to try it (I do not recommend to try it in production – and if you do, its at your own risk).
So, here we go. The third tool is a tool that you run before starting your test-phase and compatibility-phase of every new Windows 10 release. The main point of it is to create a “randomized” (more on that later) group that could change between every release – even though many devices and persons probably still would be included every time. So why do this with a script?
- The randomization, which some my not agree on, creates a better test group than if you would have the same group over and over again, or manually create one every time.
- It saves time – obviously – to create test groups is (in my experience) time consuming.
- It will keep the users happy – no one wants to be the poor user who always gets to try new things first.
- It eases the process of Windows Servicing in your organization – just as with anything when it comes to automation, or processes in general, the more things/parts you can create a standardized step for – the better.
- Lastly – its not bias. It cloud include the CEO, it could include that user that always gives you a hard time, it could exclude people that you never hear back from (and if you don’t hear anything its all good, right?)
So, currently the script consist of three parts:
- A bunch of functions from the Intune product group (available here) and Nickolaj Andersen, but also, something of (kind of) my own. I’ve modified one of the existing Powershell functions (Add-ApplicationAssignment) to be more inclusive and handle other assignments rather than group assignments. But it will also remove any group assignments where the Azure AD group has been deleted. I’ve split this into two functions/cmdlets of my own, where one is a part of this script (the other one is saved for a later blogpost). The included one will loop through all apps, prior to doing the selection of devices for the compatibility group, and remove “orphaned” assignments – while keeping all others (including exclusion). I’ve given it the rather boring name: “Remove-EmptyApplicationAssignments”. Ill, as with the other cmdlet, dig deeper into these in a follow up post.
The function can be found below. I’ve done tweaks and added some parts, but the real work has been done by the Intune team.
</span> Function Remove-EmptyApplicationAssignments(){ [cmdletbinding()] $graphApiVersion = "Beta" try { $AllApps = (Get-IntuneApplication).id foreach ($ApplicationId in $AllApps) { $AssignedGroups = (Get-ApplicationAssignment -ApplicationId $ApplicationId).assignments if($AssignedGroups){ $App_Count = @($AssignedGroups).count $i = 1 # Creating header of JSON File $JSON = @" { "mobileAppAssignments": [ "@ # Looping through all existing assignments and adding them to the JSON object foreach($Assignment in $AssignedGroups){ $ExistingTargetGroupId = $Assignment.target.GroupId $ExistingInstallIntent = $Assignment.intent # Finding out if the assignment is targeted to All User or All Devices and adding it to JSON object if(!$ExistingTargetGroupId){ if ($Assignment.target.'@odata.type' -match '#microsoft.graph.allLicensedUsersAssignmentTarget') { $JSON += @" { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.allLicensedUsersAssignmentTarget" }, "intent": "$ExistingInstallIntent" "@ } elseif ($Assignment.target.'@odata.type' -match '#microsoft.graph.allDevicesAssignmentTarget') { $JSON += @" { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.allDevicesAssignmentTarget" }, "intent": "$ExistingInstallIntent" "@ } } # Testing the Azure AD group object ID to see if the group exist in Azure AD. If not, the foreach loop will exit without adding the assignment to JSON. Note, this will throw an error message created from Get-AADGroup, but will continue else{ try { Get-AADGroup -id $ExistingTargetGroupId -ErrorAction SilentlyContinue | Out-Null } catch { 'This group does not exist in Azure AD' } # Finding out if the assignment is an exclusion of an Azure AD Group and adding the assignment to JSON if($Assignment.target.'@odata.type' -match '#microsoft.graph.exclusionGroupAssignmentTarget'){ $JSON += @" { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.exclusionGroupAssignmentTarget", "groupId": "$ExistingTargetGroupId" }, "intent": "$ExistingInstallIntent" "@ } # Adding the group assignment to JSON else{ $JSON += @" { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.groupAssignmentTarget", "groupId": "$ExistingTargetGroupId" }, "intent": "$ExistingInstallIntent" "@ } } if($i -ne $App_Count){ $JSON += @" }, "@ } else { $JSON += @" } "@ } $i++ } # Adding close of JSON object $JSON += @" ] } "@ $Resource = "deviceAppManagement/mobileApps/$ApplicationId/assign" $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" } } } # This will throw an error message if try fails - but not if the message is $null catch { $ex = $_.Exception if($ex) { $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)" write-host break } } } <span style="display:inline !important;float:none;background-color:transparent;color:#3d596d;cursor:text;font-family:'Noto Serif', Georgia, 'Times New Roman', Times, serif;font-size:16px;font-style:normal;font-variant:normal;font-weight:400;letter-spacing:normal;line-height:19.2px;orphans:2;text-align:left;text-decoration:none;text-indent:0;text-transform:none;white-space:normal;word-spacing:0;">
- The second part is the first part where things actually happens. The first part of the script focuses on hardware, and will use inventory data from Intune to get all existing hardware models. It will then choose one unique device and add that, its user and all of the users Windows devices to the application compatibility group. If you would like to know more about these groups, please read my blogpost on “The first tool in the toolbox”.
- The third part is probably the most complex one. This one looks at all applications that can be targeted to Windows devices (Apps, MSI & Office ProPlus deployments) and fetches all devices and users in each of the assigned groups. (Note! One of the things on the roadmap is to handle assignments of apps only to All Devices/All Users – which currently is ignored) It will then compare the already added users/devices in the compatibility group and thereafter choose two objects (users and/or devices) to add. It will then follow the same principle as with models. If a device is added, its user and that users other Windows devices will be added as well. If a user is added, all of that users Windows devices are added.
This is where the script ends currently. Ill first answer a question that I believe you are asking yourself – why all theses machines/users? There are many reasons for that, but my thinking goes like this:
Overall, the compatibility group should be about 10-15 % of your organization. We should aim to cover all: Models, Applications, Locations, Networks, Policies/OUs, which probably gets us to 10-15 % or close to it.
The reason for including all of a users devices is to ensure that we actually get a device with the application tested. Some devices may be old, some may not be used for a specific purpose etc. Also, this ensure a consistent user experience for the user. In the same way as my second tool (Self-Service deferral) deferred all devices when a user were added to the group, the same inclusion applies here.
Its also to do with reporting, ease of information, management and reporting. As Intune, by its own and currently (Power BI can help you a lot here) aren’t as extensive reporting-wise as ConfigMgr – I’ve choose to create groups that you would be able to report on and review easier. I’m however happy to get your feedback on this design decision.
I can keep going, but as this is a 0.5 post – ill leave you here with the to-do list and the script. I’m happy for all kinds of feedback and hope you either can use the script, or get inspiration from it and create something great on your own. The best thing about this particular script so far is that I get to learn a lot about Graph, GitHub and JSON – which will come in handy moving forward as well.
And the last note! Review the script closely – some variables are hardcoded atm and some are fetched from Azure Automation. As this is a work in progress, you may need to do some tweaks, add variables etc to make it work. The script will also throw an error message generated from the Get-AADGroup cmdlet when it finds a group that doesn’t exist. This is fine, but doesn’t look to good. I’m happy to assist if needed! Just reach out to me on Twitter or via comments.
AGAIN – DO NOT run this in production if you aren’t absolutely sure about what you are doing. I’m happy to help explaining, but I wont take any responsibility if it breaks something.
To-Do (before 1.0):
- Complete the script with other variables (as much as possible at least): Location, Network, policies etc.
- Support for all users/all devices in the app section.
- Clean it up! Variables, indentation, work notes etc
- Document it!
- Extended error handling.
The code is of course also available on GitHub!
</span> #Author: Simon Binder #Blog: bindertech.se #Twitter: @Bindertech #Thanks to: @daltondhcp, @davefalkus, @NickolajA and the Intune product group. #Most functions is copied from the Powershell Intune Samples: https://github.com/microsoftgraph/powershell-intune-samples #Script requires the Azure AD Powershell module or the Azure AD Preview Powershell module to run # Import required modules try { Import-Module -Name AzureAD -ErrorAction Stop Import-Module -Name PSIntuneAuth -ErrorAction Stop } catch { Write-Warning -Message "Failed to import modules" } function 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-AADDevice(){ [cmdletbinding()] param ( $DeviceID ) # Defining Variables $graphApiVersion = "v1.0" $Resource = "devices" try { $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)?`$filter=deviceId eq '$DeviceID'" (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)" write-host break } } #################################################### Function Get-ManagedDevices(){ [cmdletbinding()] param ( [switch]$IncludeEAS, [switch]$ExcludeMDM ) # Defining Variables $graphApiVersion = "beta" $Resource = "deviceManagement/managedDevices" try { $Count_Params = 0 if($IncludeEAS.IsPresent){ $Count_Params++ } if($ExcludeMDM.IsPresent){ $Count_Params++ } if($Count_Params -gt 1){ write-warning "Multiple parameters set, specify a single parameter -IncludeEAS, -ExcludeMDM or no parameter against the function" Write-Host break } elseif($IncludeEAS){ $uri = "https://graph.microsoft.com/$graphApiVersion/$Resource" } elseif($ExcludeMDM){ $uri = "https://graph.microsoft.com/$graphApiVersion/$Resource`?`$filter=managementAgent eq 'eas'" } else { $uri = "https://graph.microsoft.com/$graphApiVersion/$Resource`?`$filter=managementAgent eq 'mdm' and managementAgent eq 'easmdm'" Write-Warning "EAS Devices are excluded by default, please use -IncludeEAS if you want to include those devices" Write-Host } (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)" write-host break } } #################################################### Function Get-ManagedDeviceUser(){ [cmdletbinding()] param ( [Parameter(Mandatory=$true,HelpMessage="DeviceID (guid) for the device on must be specified:")] $DeviceID ) # Defining Variables $graphApiVersion = "beta" $Resource = "deviceManagement/manageddevices('$DeviceID')?`$select=userId" try { $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" Write-Verbose $uri (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).userId } 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)" write-host break } } #################################################### Function Get-IntuneApplication(){ [cmdletbinding()] $graphApiVersion = "Beta" $Resource = "deviceAppManagement/mobileApps" try { $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value | ? { (!($_.'@odata.type').Contains("managed")) } } catch { $ex = $_.Exception Write-Host "Request to $Uri failed with HTTP Status $([int]$ex.Response.StatusCode) $($ex.Response.StatusDescription)" -f Red $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)" write-host break } } #################################################### Function Get-ApplicationAssignment(){ [cmdletbinding()] param ( $ApplicationId ) $graphApiVersion = "Beta" $Resource = "deviceAppManagement/mobileApps/$ApplicationId/?`$expand=categories,assignments" try { if(!$ApplicationId){ write-host "No Application Id specified, specify a valid Application Id" -f Red break } else { $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get) } } 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)" write-host break } } #################################################### Function Remove-EmptyApplicationAssignments(){ [cmdletbinding()] $graphApiVersion = "Beta" try { $AllApps = (Get-IntuneApplication).id foreach ($ApplicationId in $AllApps) { $AssignedGroups = (Get-ApplicationAssignment -ApplicationId $ApplicationId).assignments if($AssignedGroups){ $App_Count = @($AssignedGroups).count $i = 1 # Creating header of JSON File $JSON = @" { "mobileAppAssignments": [ "@ # Looping through all existing assignments and adding them to the JSON object foreach($Assignment in $AssignedGroups){ $ExistingTargetGroupId = $Assignment.target.GroupId $ExistingInstallIntent = $Assignment.intent # Finding out if the assignment is targeted to All User or All Devices and adding it to JSON object if(!$ExistingTargetGroupId){ if ($Assignment.target.'@odata.type' -match '#microsoft.graph.allLicensedUsersAssignmentTarget') { $JSON += @" { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.allLicensedUsersAssignmentTarget" }, "intent": "$ExistingInstallIntent" "@ } elseif ($Assignment.target.'@odata.type' -match '#microsoft.graph.allDevicesAssignmentTarget') { $JSON += @" { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.allDevicesAssignmentTarget" }, "intent": "$ExistingInstallIntent" "@ } } # Testing the Azure AD group object ID to see if the group exist in Azure AD. If not, the foreach loop will exit without adding the assignment to JSON. Note, this will throw an error message created from Get-AADGroup, but will continue else{ try { Get-AADGroup -id $ExistingTargetGroupId -ErrorAction SilentlyContinue | Out-Null } catch { 'This group does not exist in Azure AD' } # Finding out if the assignment is an exclusion of an Azure AD Group and adding the assignment to JSON if($Assignment.target.'@odata.type' -match '#microsoft.graph.exclusionGroupAssignmentTarget'){ $JSON += @" { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.exclusionGroupAssignmentTarget", "groupId": "$ExistingTargetGroupId" }, "intent": "$ExistingInstallIntent" "@ } # Adding the group assignment to JSON else{ $JSON += @" { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.groupAssignmentTarget", "groupId": "$ExistingTargetGroupId" }, "intent": "$ExistingInstallIntent" "@ } } if($i -ne $App_Count){ $JSON += @" }, "@ } else { $JSON += @" } "@ } $i++ } # Adding close of JSON object $JSON += @" ] } "@ $Resource = "deviceAppManagement/mobileApps/$ApplicationId/assign" $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" } } } # This will throw an error message if try fails - but not if the message is $null catch { $ex = $_.Exception if($ex) { $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)" write-host break } } } #################################################### Function Get-AADGroup(){ [cmdletbinding()] param ( $GroupName, $id, [switch]$Members ) # Defining Variables $graphApiVersion = "v1.0" $Group_resource = "groups" try { if($id){ $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)?`$filter=id eq '$id'" (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value } elseif($GroupName -eq "" -or $GroupName -eq $null){ $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)" (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value } else { if(!$Members){ $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)?`$filter=displayname eq '$GroupName'" (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value } elseif($Members){ $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)?`$filter=displayname eq '$GroupName'" $Group = (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value if($Group){ $GID = $Group.id $Group.displayName write-host $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)/$GID/Members" (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)" write-host break } } #################################################### # Read credentials and variables $AADCredential = Get-AutomationPSCredential -Name "WaaSAccount" $Credential = Get-AutomationPSCredential -Name "WaaSAccount" $AppClientID = Get-AutomationVariable -Name "AppClientID" $WindowsVersion = Get-AutomationVariable -Name "WindowsVersion" $Tenantname = Get-AutomationVariable -Name "Tenantname" $DefaultGroup = Get-AutomationVariable -Name "DefaultGroup" $Groups = New-object Microsoft.Open.AzureAD.Model.GroupIdsForMembershipCheck $TargetOSVersion = Get-AutomationVariable -name "TargetOSVersion" #Replace with Automation Variables before production $TechPilot = '758ba924-7961-4453-a3bf-da6040cea6d3' $ApplicationPilot = '3141ba35-8f3d-41df-bd5c-4633c971af65' $DeploymentRings = '836c8ef0-a604-4d5d-8d9b-ca92fedcd573','8f7b9464-4293-4929-92cd-a7cb28eeb8c3' #Required for now, its the Windows 10 buildnumber without dots $TargetOSVersion = 10017134165 # Acquire authentication token try { Write-Output -InputObject "Attempting to retrieve authentication token" $AuthToken = Get-MSIntuneAuthToken -TenantName $Tenantname -ClientID $AppClientID -Credential $Credential if ($AuthToken -ne $null) { Write-Output -InputObject "Successfully retrieved authentication token" } } catch { Write-Warning -Message "Failed to retrieve authentication token" } Connect-AzureAD -Credential $AADCredential #Getting all Windows Devices from Intune, removing already upgraded machines. $AllWindowsDevices = Get-ManagedDevices | Where-Object -Property 'OperatingSystem' -EQ 'Windows' $AllOldWindowsDevices = New-Object 'System.Collections.Generic.List[System.Object]' foreach ($WindowsDevice in $AllWindowsDevices) { $NewVersion = $WindowsDevice | Select-Object -ExpandProperty osVersion $Newversion = $NewVersion -replace '\.','' $WindowsDevice.osVersion="$NewVersion" if ($WindowsDevice.osVersion -lt $TargetOSVersion) { $AllOldWindowsDevices.Add($WindowsDevice) } } #Populating the application pilot group. #Getting all unique models, selecting machines at random. Finding the Intune-device owner and adding it and all of its devices to the Application Pilot group. $AllModels = $AlloldWindowsDevices | Sort-Object -Property model -Unique | Select-Object Devicename, model, id, osVersion foreach ($Model in $AllModels) { $OwnerID = Get-ManagedDeviceUser -DeviceID $Model.id $Owner = Get-AzureADUser -ObjectID $OwnerID #Ensure to add device and user to group try { Add-AzureADGroupMember -ObjectId $ApplicationPilot -RefObjectId $Owner.ObjectId } catch {$DisplayName = $Owner.DisplayName "$Displayname is already a member of this group"} $MemberDevices = Get-AzureADUserOwnedDevice -ObjectId $OwnerID | Where-Object {$_.DeviceOSType -eq 'Windows'} | Select-Object -ExpandProperty ObjectID foreach ($MemberDevice in $MemberDevices) { try { Add-AzureADGroupMember -ObjectId $Groups -RefObjectId $MemberDevice } catch { "$MemberDevice is already a member of $Groups" } } } #Cleaning up all orphaned assignments prior to getting application groups. Remove-EmptyApplicationAssignments #Getting all Windows apps, LOB and O365 deployments, selecting machines at random. Finding the Intune-device owner and adding it and all of its devices to the Application Pilot group. $Groups = $ApplicationPilot $AllApps = Get-IntuneApplication $AllWindowsApps = New-Object 'System.Collections.Generic.List[System.Object]' foreach ($App in $AllApps) { $StoreApp = $App | Where-Object -Property '@odata.type' -Match '#microsoft.graph.microsoftStoreForBusinessApp' if ($StoreApp -ne $null) { $AllWindowsApps.Add($StoreApp) } $MSI = $App | Where-Object -Property '@odata.type' -Match '#microsoft.graph.windowsMobileMSI' if ($MSI -ne $null) { $AllWindowsApps.Add($MSI) } $Office = $App | Where-Object -Property '@odata.type' -Match '#microsoft.graph.officeSuiteApp' if ($Office -ne $null) { $AllWindowsApps.Add($Office) } $Appx = $App | Where-Object -Property '@odata.type' -Match '#microsoft.graph.officeSuiteApp' if ($Appx -ne $null) { $AllWindowsApps.Add($Appx) } } foreach ($WindowsApp in $AllWindowsApps) { $Group = Get-ApplicationAssignment -ApplicationId $WindowsApp.id | Select-Object -ExpandProperty 'assignments' | Select-Object -ExpandProperty 'target' if ($Group.'@odata.type' -eq $null) {"$Group This is not assigned to any group"} elseif ($Group.'@odata.type' -match '#microsoft.graph.allLicensedUsersAssignmentTarget') {'This is assigned to all Users'} elseif ($Group.'@odata.type' -match '#microsoft.graph.allDevicesAssignmentTarget') {'This is assigned to all Devices'} elseif ($Group.'@odata.type' -match '#microsoft.graph.groupAssignmentTarget') { try { $AppGroupMembers = Get-AzureADGroupMember -ObjectId $group.groupId -All:$True | Group-Object 'ObjectType' -AsHashTable -AsString } Catch {"The group with ID $Group.groupid does no longer exist"} if ($AppGroupMembers.user -ne $null) { $AppGroupUserMembers = $AppGroupMembers $CurrentMembers = Get-AzureADGroupMember -ObjectId $Groups -All:$True | Group-Object 'ObjectType' -AsHashTable -AsString if ($CurrentMembers -ne $null) { $Compared = Compare-Object -ReferenceObject $AppGroupUserMembers.user.objectid -DifferenceObject $CurrentMembers.user.objectid -IncludeEqual | Where-Object -Property 'SideIndicator' -Match '==' | Select-Object -ExpandProperty 'InputObject' foreach ($Object in $Compared) { if ($AppGroupUserMembers.user.objectid -contains $Object) {$AppGroupUserMembers = $AppGroupUserMembers.User.ObjectID -ne $Compared } $RandomMembers = $AppGroupUserMembers | Random -Count 2 foreach ($Member in $RandomMembers) { $MemberDevices = Get-AzureADUserOwnedDevice -ObjectId $Member | Where-Object {$_.DeviceOSType -eq 'Windows'} | Select-Object -ExpandProperty ObjectID foreach ($MemberDevice in $MemberDevices) { try { Add-AzureADGroupMember -ObjectId $Groups -RefObjectId $MemberDevice } catch { "$MemberDevice is already a member of $Groups" } } try { Add-AzureADGroupMember -ObjectId $Groups -RefObjectId $Member } catch { "$MemberDevice is already a member of $Groups" } } } } } if ($AppGroupMembers.Device -ne $null) { $AppGroupDeviceMembers = $AppGroupMembers $CurrentMembers = Get-AzureADGroupMember -ObjectId $Groups -All:$True | Group-Object 'ObjectType' -AsHashTable -AsString if ($CurrentMembers -ne $null) { $Compared = Compare-Object -ReferenceObject $AppGroupDeviceMembers.Device.objectid -DifferenceObject $CurrentMembers.Device.objectid -IncludeEqual | Where-Object -Property 'SideIndicator' -Match '==' | Select-Object -ExpandProperty 'InputObject' foreach ($Object in $Compared) { if ($AppGroupDeviceMembers.user.objectid -contains $Object) {$AppGroupDeviceMembers = $AppGroupDeviceMembers.User.ObjectID -ne $Compared } $RandomMembers = $AppGroupDeviceMembers | Random -Count 2 foreach ($Member in $RandomMembers) { $MemberUsers = Get-ManagedDeviceUser -DeviceID $AppGroupDeviceMembers.Device.DeviceID #May need to add code to handle multiple owners foreach ($MemberUser in $MemberUsers) { try { Add-AzureADGroupMember -ObjectId $Groups -RefObjectId $MemberUser } catch { "$MemberUser is already a member of $Groups" } } try { Add-AzureADGroupMember -ObjectId $Groups -RefObjectId $Member } catch { "$Member is already a member of $Groups" } } } } } } } <span style="display:inline !important;float:none;background-color:transparent;color:#3d596d;cursor:text;font-family:'Noto Serif', Georgia, 'Times New Roman', Times, serif;font-size:16px;font-style:normal;font-variant:normal;font-weight:400;letter-spacing:normal;line-height:19.2px;orphans:2;text-align:left;text-decoration:none;text-indent:0;text-transform:none;white-space:normal;word-spacing:0;">
Leave a Reply