diff --git a/IntuneWinAppUtil.exe b/IntuneWinAppUtil.exe new file mode 100644 index 0000000..566b5e0 Binary files /dev/null and b/IntuneWinAppUtil.exe differ diff --git a/PackageMyPrinters.ps1 b/PackageMyPrinters.ps1 new file mode 100644 index 0000000..d57959b --- /dev/null +++ b/PackageMyPrinters.ps1 @@ -0,0 +1,275 @@ +Set-Location -Path $PSScriptRoot +#Log Dir +$LogDir = ".\Logs" +New-Item -ItemType Directory -Path $LogDir -Force | Out-Null +$LogPath = Join-Path $LogDir "SkippedPrinters.csv" + +# Show installed printers and let an admin pick one or many +$SelectedPrinters = Get-Printer | Out-GridView -Title 'Select Printers to Migrate to Intune' -PassThru +Select-Object Name,DriverName,PortName,ComputerName,ShareName | + +#Create Directory Structure +New-Item -Path .\Drivers -ItemType Directory + +if (-not $SelectedPrinters) { + Write-Host "No printers selected." -ForegroundColor Yellow + return +} + +# Get default B&W Flag +$defaultbw = Read-Host "**Do you want to default printers to black and white? (Type Y or N and press enter)" +$defaultbw = $defaultbw.Trim().ToUpper() + +ForEach ($selectedprinter in $selectedprinters) { +If ($selectedprinter.ShareName) { +$PrinterName = $selectedprinter.ShareName +} +else { +$PrinterName = $selectedPrinter.Name +} +$PortName = $selectedPrinter.PortName +$PrinterIP = (Get-PrinterPort -Name $PortName).PrinterHostAddress +$DriverName = $selectedprinter.DriverName + +#Grab the 64-bit driver if there are multiple listed and check for INF path. Skip if no INF path. +$Driver = Get-PrinterDriver -Name $DriverName | Where-Object PrinterEnvironment -eq 'Windows x64' | Select-Object -First 1 + +#Skip if no driver: +if (!$Driver) { +$logskipped = [pscustomobject]@{ + PrinterName = $PrinterName + DriverName = $DriverName + PortName = $PortName + PrinterIP = $PrinterIP + PrinterEnvironment = $Driver.PrinterEnvironment + InfPath = $Driver.InfPath + Reason = "No driver object present" + } +$logskipped | Export-Csv -Path $LogPath -Append -NoTypeInformation +Write-Host "Skipping '$printername' - There's no driver object found. See log for additional details." -ForegroundColor Red +continue +} + +#Skip Driver export if an integrated Windows/Microsoft driver (IPP or similar) +if ($Driver.Manufacturer -eq 'Microsoft') { +$logskipped = [pscustomobject]@{ + PrinterName = $PrinterName + DriverName = $DriverName + PortName = $PortName + PrinterIP = $PrinterIP + PrinterEnvironment = $Driver.PrinterEnvironment + InfPath = $Driver.InfPath + Reason = "Microsoft driver - will be provided by OS/Windows Update" + } + +$logskipped | Export-Csv -Path $LogPath -Append -NoTypeInformation +Write-Host "Skipping driver export for '$PrinterName' - Microsoft class driver (no export needed)" -ForegroundColor Yellow +continue +} + +#Skip drivers with no exportable INF + if (!$Driver.InfPath) { +$logskipped = [pscustomobject]@{ + PrinterName = $PrinterName + DriverName = $DriverName + PortName = $PortName + PrinterIP = $PrinterIP + PrinterEnvironment = $Driver.PrinterEnvironment + InfPath = $Driver.InfPath + Reason = "INF Path Empty. Cannot export with this script" + } +$logskipped | Export-Csv -Path $LogPath -Append -NoTypeInformation +Write-Host "Skipping driver export for '$PrinterName' - Microsoft class driver (no export needed)" -ForegroundColor Yellow +continue +} + +$DriverPath = $Driver.InfPath | Split-Path +$DriverINF = $Driver.InfPath | Split-Path -Leaf + +#Copy Driver Files to directory for packaging +If (Test-Path .\Drivers\$Drivername) { +Write-Host "Driver directory already exist" +} +Else { +$DriverDir = New-Item -Path .\ExportedPrinters\$PrinterName\driver -ItemType Directory +Copy-Item -Path "$DriverPath\*" -Destination $DriverDir -Recurse + +#Generate Install script for the printer +$PkgDir = ".\ExportedPrinters\$PrinterName" +$InstallPath = Join-Path $PkgDir 'Install.ps1' +New-Item -ItemType Directory -Path $PkgDir -Force | Out-Null # in case folder wasn't created yet +Copy-Item -path "$PSScriptRoot\printericon.jpg" -Destination $PkgDir +If ($defaultbw.Trim().ToUpper() -eq "Y") { +$install = @" +# Auto-generated installer for: $PrinterName + +`$DriverINF = '$DriverINF' +`$DriverName = '$DriverName' +`$PortName = '$PortName' +`$PrinterIP = '$PrinterIP' +`$PrinterName = '$PrinterName' + +pnputil.exe /add-driver ".\Driver\`$DriverINF" /install +Add-PrinterDriver -Name `$drivername -ErrorAction SilentlyContinue + +`$checkPortExists = Get-Printerport -Name `$portname -ErrorAction SilentlyContinue + +if (-not `$checkPortExists) +{ +Add-PrinterPort -name `$portName -PrinterHostAddress `$PrinterIP +} + +`$printDriverExists = Get-PrinterDriver -name `$DriverName -ErrorAction SilentlyContinue + +if (`$printDriverExists) +{ +Add-Printer -Name `$PrinterName -PortName `$portName -DriverName `$DriverName +} +else +{ +Write-Warning "Printer Driver not installed" +} + +Set-PrintConfiguration -PrinterName `$PrinterName -Color `$false + +SLEEP 30 +"@ +} +If ($defaultbw.Trim().ToUpper() -ne "Y") { +$install = @" +# Auto-generated installer for: $PrinterName + +`$DriverINF = '$DriverINF' +`$DriverName = '$DriverName' +`$PortName = '$PortName' +`$PrinterIP = '$PrinterIP' +`$PrinterName = '$PrinterName' + +pnputil.exe /add-driver ".\Driver\`$DriverINF" /install +Add-PrinterDriver -Name `$drivername -ErrorAction SilentlyContinue + +`$checkPortExists = Get-Printerport -Name `$portname -ErrorAction SilentlyContinue + +if (-not `$checkPortExists) +{ +Add-PrinterPort -name `$portName -PrinterHostAddress `$PrinterIP +} + +`$printDriverExists = Get-PrinterDriver -name `$DriverName -ErrorAction SilentlyContinue + +if (`$printDriverExists) +{ +Add-Printer -Name `$PrinterName -PortName `$portName -DriverName `$DriverName +} +else +{ +Write-Warning "Printer Driver not installed" +} + +SLEEP 30 +"@ +} + +Set-Content -Path $InstallPath -Value $install -Encoding UTF8 -Force +Write-Host "Created: $InstallPath" -ForegroundColor Green +} + +#Generate Detection Script +$detectionPath = Join-Path $pkgDir 'detection.ps1' +$detectionScript = @" +`$PrinterName = "$PrinterName" + +`$printerinstalled = Get-Printer -Name "`$PrinterName" -ErrorAction SilentlyContinue +if (`$printerinstalled) { + Write-Output "Printer exists" + exit 0 +} +else { + Write-Output "Printer not installed" + exit 1 +} +"@ + +Set-Content -Path $detectionPath -Value $detectionScript -Encoding UTF8 -Force +Write-Host "Created: $detectionPath" -ForegroundColor Cyan + +#Generate Uninstall Script +$uninstallPath = Join-Path $pkgDir 'uninstall.ps1' +$uninstallScript = @" +`$PrinterName = "$PrinterName" +`Remove-Printer -Name "`$PrinterName" +"@ + +Set-Content -Path $uninstallPath -Value $uninstallScript -Encoding UTF8 -Force +Write-Host "Created: $uninstallPath" -ForegroundColor Cyan + +#Build a manifest file with printer details +$manifest = @{ + PrinterName = $PrinterName + DriverName = $DriverName + DriverINF = $DriverINF + PortName = $PortName + PrinterIP = $PrinterIP + DriverSourcePath = $DriverPath + ExportedOnLocal = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss zzz") +} + +#Save as manifest.json next to install.ps1 +$manifestPath = Join-Path $pkgDir 'manifest.json' +$manifest | ConvertTo-Json -Depth 5 | Set-Content -Path $manifestPath -Encoding UTF8 + +Write-Host "Created manifest: $manifestPath" -ForegroundColor Cyan +} + + +#Grab the IntuneWinAppUtil +$dest = Join-Path (Get-Location) "IntuneWinAppUtil.exe" +$url = "https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool/raw/master/IntuneWinAppUtil.exe" + +Write-Host "Downloading IntuneWinAppUtil.exe from GitHub..." + + Invoke-WebRequest -Uri $url -OutFile $dest + +if (Test-Path $dest) { + Write-Host "Download successful" -ForegroundColor Green +} else { + Write-Warning "Download failed. Please check the URL or your network." -Foreground Red +} +# Unblock the downloaded exe +Unblock-File -Path $dest -ErrorAction SilentlyContinue + +# Ensure it's executable +Start-Sleep 1 + + +#Package Printer Files for Win32 App Deployment +$IntuneWinAppUtil = ".\IntuneWinAppUtil.exe" +$SourcePath = ".\ExportedPrinters" +$PrinterFolders = Get-Childitem $SourcePath -Directory + +foreach ($PrinterFolder in $PrinterFolders) { + $setupFolder = $PrinterFolder.FullName + $printerName = $PrinterFolder.Name + $setupFile = "install.ps1" + $setupPath = Join-Path $setupFolder $setupFile + + if (-not (Test-Path $setupPath)) { + Write-Warning "No install.ps1 in $setupFolder - skipping" + continue + } + + Write-Host "Packaging $printerName for Intune deployment..." -ForegroundColor Cyan + & $IntuneWinAppUtil -c $setupFolder -s $setupFile -o $setupFolder -q + Write-Host "Successfully Created .intunewin for $printername" -ForegroundColor Green +} + +$runuploadscript = Read-Host "**All printers & drivers have been exported and packaged for Intune upload. Do you want to upload these printers now?** (Type Y or N and press enter)" +$runuploadscript = $runuploadscript.Trim().ToUpper() +If ($runuploadscript.Trim().ToUpper() -eq "Y") { + Write-Host "User entered $runuploadscript - running script to upload printers to Intune" -ForegroundColor Yellow + Start-Sleep 2 + & .\UploadIntuneWinPrinters.ps1 +} +else { + Write-Host "User entered $runuploadscript - ending script" -ForegroundColor Yellow +} \ No newline at end of file diff --git a/UploadIntuneWinPrinters.ps1 b/UploadIntuneWinPrinters.ps1 new file mode 100644 index 0000000..ea2a28a --- /dev/null +++ b/UploadIntuneWinPrinters.ps1 @@ -0,0 +1,311 @@ +function Upload-IntuneWinFile { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$FilePath, + + [Parameter(Mandatory)] + [string]$SasUrl, + + [int]$ChunkSizeBytes = 16MB, + + [switch]$SendContentMD5 + ) + + if (-not (Test-Path $FilePath)) { + throw "File not found: $FilePath" + } + + $blockIds = New-Object System.Collections.Generic.List[string] + $totalBytesSent = 0L + + $handler = New-Object System.Net.Http.HttpClientHandler + $client = New-Object System.Net.Http.HttpClient($handler) + $client.Timeout = [TimeSpan]::FromMinutes(30) + + $fs = [System.IO.File]::OpenRead($FilePath) + try { + $index = 0 + $buffer = New-Object byte[] $ChunkSizeBytes + + while (($read = $fs.Read($buffer, 0, $buffer.Length)) -gt 0) { + $blockIdPlain = ($index).ToString("0000000000") + $blockIdB64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($blockIdPlain)) + $blockIds.Add($blockIdB64) | Out-Null + + $blockUri = "{0}&comp=block&blockid={1}" -f $SasUrl, [Uri]::EscapeDataString($blockIdB64) + + $chunkBytes = New-Object byte[] $read + [System.Buffer]::BlockCopy($buffer, 0, $chunkBytes, 0, $read) + + $content = New-Object System.Net.Http.ByteArrayContent -ArgumentList (, $chunkBytes) + + if ($SendContentMD5) { + $md5 = [System.Security.Cryptography.MD5]::Create().ComputeHash($chunkBytes) + $md5b64 = [Convert]::ToBase64String($md5) + $content.Headers.Add("Content-MD5", $md5b64) + } + + $resp = $client.PutAsync($blockUri, $content).GetAwaiter().GetResult() + if (-not $resp.IsSuccessStatusCode) { + $err = $resp.Content.ReadAsStringAsync().GetAwaiter().GetResult() + throw "Put Block failed at index $index (size=$read, HTTP $([int]$resp.StatusCode)): $err" + } + + $totalBytesSent += $read + $index++ + } + } + finally { + $fs.Dispose() + } + + $sb = New-Object System.Text.StringBuilder + [void]$sb.Append('') + $blockIds | ForEach-Object { [void]$sb.Append("$($_)") } + [void]$sb.Append('') + $xml = $sb.ToString() + + $uriList = "$SasUrl&comp=blocklist" + $xmlContent = New-Object System.Net.Http.StringContent($xml, [Text.Encoding]::UTF8, "application/xml") + + $xmlContent.Headers.Remove("Content-Type") | Out-Null + $xmlContent.Headers.Add("Content-Type","application/xml") + + $req = New-Object System.Net.Http.HttpRequestMessage([System.Net.Http.HttpMethod]::Put, $uriList) + $req.Content = $xmlContent + $req.Headers.Add("x-ms-version","2019-12-12") + + $resp2 = $client.SendAsync($req).GetAwaiter().GetResult() + if (-not $resp2.IsSuccessStatusCode) { + $err2 = $resp2.Content.ReadAsStringAsync().GetAwaiter().GetResult() + throw "Put Block List failed (HTTP $([int]$resp2.StatusCode)): $err2" + } + + [pscustomobject]@{ + FilePath = $FilePath + FileLength = (Get-Item $FilePath).Length + ChunkSizeBytes = $ChunkSizeBytes + BlocksUploaded = $blockIds.Count + BytesSent = $totalBytesSent + Finalized = $true + } +} +Start-Transcript -path ".\UploadIntuneWinPrinters.log" + +$modules = 'Microsoft.Graph.Authentication', 'Microsoft.Graph.DeviceManagement', 'Microsoft.Graph.Beta.DeviceManagement' +Write-Host -ForegroundColor DarkYellow "Installing Required Modules if they're missing..." +Foreach ($module in $modules) { +if (Get-Module -ListAvailable -Name $module) { + Write-Host -ForegroundColor Yellow "$module module is already installed" +} +else { + Write-Host -ForegroundColor Yellow "Installing the $module Module for Current User" + Install-Module -Name $module -Scope CurrentUser -Force + Write-Host "Installed $module module for current user" +} +} + +#Login to Graph +$scopes = @( + "DeviceManagementApps.ReadWrite.All", + "DeviceManagementConfiguration.ReadWrite.All", + "Files.ReadWrite.All" +) +Connect-MgGraph -Scopes $scopes + +#Root where all printer packages live +$source = ".\ExportedPrinters" + +#Get Base64 value of image for app icon +$imagefile = ".\printericon.jpg" +$imageBytes = [System.IO.File]::ReadAllBytes((Resolve-Path $imageFile)) +$imageBase64 = [Convert]::ToBase64String($imageBytes) + +#Find out if we should make printers available to the all users group +$assignmentresponse = Read-Host "Do you want to assign the printers as available to All Users? (Type Y or N)" +$assignmentresponse = $assignmentresponse.Trim().ToUpper() +If ($assignmentresponse -eq "Y") { + Write-Host "User entered $assignmentresponse - Printers will be assigned as available to All Users" -ForegroundColor Yellow +} +else { + Write-Host "User entered $assignmentresponse - Printers will be NOT be assigned" -ForegroundColor Yellow +} + +Start-Sleep 2 + +#Loop each printer folder and create Win32 App +$PrinterFolders = Get-ChildItem $source -Directory +:printerloop foreach ($Printerfolder in $PrinterFolders) { + $printerName = $Printerfolder.Name + $intunewinPath = Join-Path $Printerfolder.FullName "install.intunewin" + $appdetectPath = Join-Path $Printerfolder.FullName "detection.ps1" + + if (-not (Test-Path $intunewinPath)) { + Write-Warning "No .intunewin found for $printerName — skipping" + continue + } + if (-not (Test-Path $appdetectPath)) { + Write-Warning "No detection.ps1 for $printerName — skipping" + continue + } + Write-Host "Creating Win32 App Shell for $printerName..." -ForegroundColor Cyan + + #Create Win32 app shell + $body = @{ + "@odata.type" = "#microsoft.graph.win32LobApp" + displayName = "Printer - $printerName" + description = "Installs printer $printerName with packaged driver." + publisher = "SMBtotheCloud" + isFeatured = $false + setupFilePath = "install.ps1" + fileName = "install.intunewin" + installCommandLine = '%windir%\sysnative\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoProfile -File .\install.ps1' + uninstallCommandLine = 'powershell.exe -executionpolicy bypass .\uninstall.ps1' + installExperience = @{ runAsAccount = "system" } + applicableArchitectures = "x64" + minimumSupportedOperatingSystem = @{ v10_2004 = $true } + detectionRules = @( + @{ + "@odata.type" = "#microsoft.graph.win32LobAppPowerShellScriptDetection" + scriptContent = [Convert]::ToBase64String([IO.File]::ReadAllBytes($appdetectPath)) + enforceSignatureCheck = $false + runAs32Bit = $false + } + ) + returnCodes = @( + @{ type = "success"; returnCode = 0 }, + @{ type = "success"; returnCode = 3010 }, + @{ type = "failed"; returnCode = 1603 } + ) + largeIcon = @{ + type = "image/png" + value = "$imageBase64" + } + } + +#Send Win32 App Shell and get appID +$app = Invoke-MgGraphRequest -Method POST -Uri "/beta/deviceAppManagement/mobileApps" -Body ($body | ConvertTo-Json -Depth 10 -Compress) +$appId = $app.id + +#Create directory for extracted .intunewin contents, set locations and get info from detectionXML +$tempRoot = Join-Path (Get-Location).Path "ExtractedIntuneWinFiles" +$safePrinterName = ($PrinterName -replace '[<>:"/\\|?*]', '_') +$temp = Join-Path $tempRoot ("$safePrinterName" + "_" + $appId) +New-Item -ItemType Directory -Path $temp -Force | Out-Null +Add-Type -AssemblyName System.IO.Compression.FileSystem +[System.IO.Compression.ZipFile]::ExtractToDirectory($intunewinPath, $temp) #need to use this because of file extension +[xml]$detectionxml = Get-Content (Join-Path $temp 'IntuneWinPackage\Metadata\Detection.xml') +$unencryptedSize = [int64]$detectionxml.ApplicationInfo.UnencryptedContentSize +$innerintunewin = Get-ChildItem -Path (Join-Path $temp 'IntuneWinPackage\Contents\') -Filter *.intunewin +$encryptedsize = [int64]$innerintunewin.Length + +#Get Encryption Info from detection.xml for commit to finish Win32App creation +$encXml = $detectionxml.ApplicationInfo.EncryptionInfo +$commitBody = @{ fileEncryptionInfo = @{ + encryptionKey = $encXml.EncryptionKey + fileDigest = $encXml.FileDigest + fileDigestAlgorithm = $encXml.FileDigestAlgorithm + initializationVector = $encXml.InitializationVector + mac = $encXml.Mac + macKey = $encXml.MacKey + profileIdentifier = $encXml.ProfileIdentifier +}} | ConvertTo-Json -Depth 10 + +#Set info for intunewin file. Need to send this to generate SAS URL. +$packagejson = @{ + name = (Split-Path $intunewinPath -Leaf) + size = $unencryptedSize + sizeEncrypted = $encryptedsize + manifest = $null + isDependency = $false + } | ConvertTo-Json -Depth 6 + +#Create new Content Version +$cv = Invoke-MgGraphRequest POST "/beta/deviceAppManagement/mobileApps/$AppId/microsoft.graph.win32LobApp/contentVersions" -Body "{}" -ContentType "application/json" +$cvId = $cv.id + +#Register file upload for new CV +$fileObj = Invoke-MgGraphRequest -Method POST -Uri "/beta/deviceAppManagement/mobileApps/$AppId/microsoft.graph.win32LobApp/contentVersions/$cvId/files" -Body $packageJson -ContentType "application/json" +$fileID = $fileobj.id +Write-Host "New Content Version $cvid has been created. Waiting for SAS URL to be generated..." -ForegroundColor Cyan + +#Wait for SAS URL to populate so we can upload intunewin +do { + Start-Sleep 2 + $fileObjstatus = Invoke-MgGraphRequest -Method GET -Uri "/beta/deviceAppManagement/mobileApps/$AppId/microsoft.graph.win32LobApp/contentVersions/$cvId/files/$($fileObj.id)" + } while (-not $fileObjstatus.azureStorageUri) +Start-Sleep 2 +Write-Host "Starting upload of intunewin file to Intune" -ForegroundColor Cyan + +#upload .intunewin file +$uploadResult = Upload-IntuneWinFile -FilePath $innerIntunewin.FullName -SasUrl $fileObjStatus.azureStorageUri -ChunkSizeBytes 16MB -SendContentMD5 +$uploadResult | Format-List + +start-sleep 3 + +#Commit App +Invoke-MgGraphRequest POST "/beta/deviceAppManagement/mobileApps/$AppId/microsoft.graph.win32LobApp/contentVersions/$cvId/files/$fileId/commit" -Body $commitBody -ContentType "application/json" + +#Point app at new CV after upload state moved to commitFileSuccess. Timeout of 90 seconds to prevent the script from hanging. +Write-Host "App committed. Waiting for file upload state to move to success before assigning content version" -ForegroundColor Cyan +$startTime = Get-Date +$timeout = [TimeSpan]::FromSeconds(90) +do { + Start-Sleep 2 + $appstatus = Invoke-MgGraphRequest GET "/beta/deviceAppManagement/mobileApps/$AppId/microsoft.graph.win32LobApp/contentVersions/$cvId/files/$fileId`?$select=id,name,isCommitted,uploadState,size,sizeEncrypted" + Write-Host "Current State" - $appstatus.uploadState + $elapsed = (Get-Date) - $startTime + if ($elapsed -ge $timeout) { + Write-Warning "Timeout after 90s while waiting for $($PrinterFolder.Name). Deleting printer app and moving onto the next printer" + try { + Invoke-MgGraphRequest -Method DELETE -Uri "/beta/deviceAppManagement/mobileApps/$AppId" + Write-Host "Deleted orphaned app for $printerName" -ForegroundColor Yellow + } + catch { + Write-Warning "Failed to delete orphaned app for $printerName : $($_.Exception.Message)" + } + continue printerloop + } + if ($appstatus.uploadState -eq "commitFileFailed") + { + Write-Warning "Commit File Failed for $printername. Deleting App and Moving on to next printer" + try { + Invoke-MgGraphRequest -Method DELETE -Uri "/beta/deviceAppManagement/mobileApps/$AppId" + Write-Host "Deleted orphaned app for $printerName" -ForegroundColor Yellow + } + catch { + Write-Warning "Failed to delete orphaned app for $printerName : $($_.Exception.Message)" + } + continue printerloop + } + } while ($appstatus.uploadstate -ne "commitFileSuccess") + +$patchbody = @{ +"@odata.type" = "#microsoft.graph.win32LobApp" +committedContentVersion = "$cvId" +} | ConvertTo-Json + +Invoke-MgGraphRequest -Method PATCH -Uri "/beta/deviceAppManagement/mobileApps/$AppId" -Body $patchbody -ContentType "application/json" + +#Verify Commited version is correct: +$app = Invoke-MgGraphRequest GET "/beta/deviceAppManagement/mobileApps/$AppId" +Write-Host "Committed version (app) = $($app.committedContentVersion) (expected $cvId)" +Write-Host "Finished creating deployment for $printername - Moving to the next printer in the list." -ForegroundColor Green + +#Assign to All Users if chosen +If ($assignmentresponse -eq "Y") { + $assignment = @{ + "@odata.type" = "#microsoft.graph.mobileAppAssignment" + intent = "available" + target = @{ + "@odata.type" = "#microsoft.graph.allLicensedUsersAssignmentTarget" + } + } | ConvertTo-Json -Depth 5 + + Invoke-MgGraphRequest -Method POST -Uri "/beta/deviceAppManagement/mobileApps/$AppId/assignments" -Body $assignment -ContentType "application/json" +} +} + +Disconnect-MgGraph +Stop-Transcript \ No newline at end of file diff --git a/printericon.jpg b/printericon.jpg new file mode 100644 index 0000000..f5fb5c7 Binary files /dev/null and b/printericon.jpg differ