enable-and-deploy-remotehelp.ps1
<#PSScriptInfo
.VERSION 1.0.2 .GUID ed937b40-9073-41c4-8ae9-4dc8fa2596d9 .AUTHOR AndrewTaylor .DESCRIPTION Enables RemoteHelp, grabs the latest installer, packages and deploys including AAD Groups .COMPANYNAME .COPYRIGHT GPL .TAGS intune endpoint MEM environment winget win32 .LICENSEURI https://github.com/andrew-s-taylor/public/blob/main/LICENSE .PROJECTURI https://github.com/andrew-s-taylor/public .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES #> <# .SYNOPSIS Enables RemoteHelp, grabs the latest installer, packages and deploys including AAD Groups .DESCRIPTION .Enables RemoteHelp on the tenant (Thanks to Aresh Sarkari) .Downloads latest version of RemoteHelp .Creates Intunewin .Creates AAD Groups .Uploads and assigns everything .INPUTS None Required .OUTPUTS In-Line Outputs .NOTES Version: 1.0.2 Author: Andrew Taylor WWW: andrewstaylor.com Creation Date: 15/03/2023 Purpose/Change: Initial script development .EXAMPLE N/A #> ########################################################################################## $ErrorActionPreference = "Continue" ##Start Logging to %TEMP%\intune.log $date = get-date -format ddMMyyyy Start-Transcript -Path $env:TEMP\intune-$date.log ############################################################################################################### ###### Install Modules ###### ############################################################################################################### Write-Host "Installing Microsoft Graph modules if required (current user scope)" #Install MS Graph if not available if (Get-Module -ListAvailable -Name Microsoft.Graph.Groups) { Write-Host "Microsoft Graph Already Installed" } else { try { Install-Module -Name Microsoft.Graph.Groups -Scope CurrentUser -Repository PSGallery -Force } catch [Exception] { $_.message exit } } #Install MS Graph if not available if (Get-Module -ListAvailable -Name Microsoft.Graph.DeviceManagement) { Write-Host "Microsoft Graph Already Installed" } else { try { Install-Module -Name Microsoft.Graph.DeviceManagement -Scope CurrentUser -Repository PSGallery -Force } catch [Exception] { $_.message exit } } #Install MS Graph if not available if (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication) { Write-Host "Microsoft Graph Already Installed" } else { try { Install-Module -Name Microsoft.Graph.Authentication -Scope CurrentUser -Repository PSGallery -Force } catch [Exception] { $_.message exit } } #Install MS Graph if not available if (Get-Module -ListAvailable -Name microsoft.graph.devices.corporatemanagement ) { Write-Host "Microsoft Graph Already Installed" } else { try { Install-Module -Name microsoft.graph.devices.corporatemanagement -Scope CurrentUser -Repository PSGallery -Force } catch [Exception] { $_.message exit } } #Importing Modules Import-Module microsoft.graph.groups import-module microsoft.graph.devicemanagement import-module microsoft.graph.authentication import-module microsoft.graph.devices.corporatemanagement ############################################################################################################### ###### Create Dir ###### ############################################################################################################### #Create path for files $DirectoryToCreate = "c:\temp" if (-not (Test-Path -LiteralPath $DirectoryToCreate)) { try { New-Item -Path $DirectoryToCreate -ItemType Directory -ErrorAction Stop | Out-Null #-Force } catch { Write-Error -Message "Unable to create directory '$DirectoryToCreate'. Error was: $_" -ErrorAction Stop } "Successfully created directory '$DirectoryToCreate'." } else { "Directory already existed" } $random = Get-Random -Maximum 1000 $random = $random.ToString() $date =get-date -format yyMMddmmss $date = $date.ToString() $path2 = $random + "-" + $date $path = "c:\temp\" + $path2 + "\" New-Item -ItemType Directory -Path $path ############################################################################################################### ###### Install Apps ###### ############################################################################################################### ##IntuneWinAppUtil $intuneapputilurl = "https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool/raw/master/IntuneWinAppUtil.exe" $intuneapputiloutput = $path + "IntuneWinAppUtil.exe" Invoke-WebRequest -Uri $intuneapputilurl -OutFile $intuneapputiloutput ############################################################################################################### ###### Add Functions ###### ############################################################################################################### function Add-MDMApplication() { <# .SYNOPSIS This function is used to add an MDM application using the Graph API REST interface .DESCRIPTION The function connects to the Graph API Interface and adds an MDM application from the itunes store .EXAMPLE Add-MDMApplication -JSON $JSON Adds an application into Intune .NOTES NAME: Add-MDMApplication #> [cmdletbinding()] param ( $JSON ) try { if (!$JSON) { Write-Error "No JSON was passed to the function, provide a JSON variable" break } Test-JSON -JSON $JSON New-MgDeviceAppMgtMobileApp -BodyParameter $JSON } catch { $ex = $_.Exception $errorResponse = $ex.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($errorResponse) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $responseBody = $reader.ReadToEnd() Write-Debug "Response content:`n$responseBody" Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" break } } #################################################### Function Add-ApplicationAssignment() { <# .SYNOPSIS This function is used to add an application assignment using the Graph API REST interface .DESCRIPTION The function connects to the Graph API Interface and adds a application assignment .EXAMPLE Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent $InstallIntent Adds an application assignment in Intune .NOTES NAME: Add-ApplicationAssignment #> [cmdletbinding()] param ( $ApplicationId, $TargetGroupId, $InstallIntent ) try { if (!$ApplicationId) { Write-Error "No Application Id specified, specify a valid Application Id" break } if (!$TargetGroupId) { Write-Error "No Target Group Id specified, specify a valid Target Group Id" break } if (!$InstallIntent) { Write-Error "No Install Intent specified, specify a valid Install Intent - available, notApplicable, required, uninstall, availableWithoutEnrollment" break } $JSON = @" { "mobileAppAssignments": [ { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.groupAssignmentTarget", "groupId": "$TargetGroupId" }, "intent": "$InstallIntent" } ] } "@ New-MgDeviceAppMgtMobileAppAssignment -BodyParameter $JSON } catch { $ex = $_.Exception $errorResponse = $ex.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($errorResponse) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $responseBody = $reader.ReadToEnd() Write-Debug "Response content:`n$responseBody" Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" break } } function CloneObject($object) { $stream = New-Object IO.MemoryStream $formatter = New-Object Runtime.Serialization.Formatters.Binary.BinaryFormatter $formatter.Serialize($stream, $object) $stream.Position = 0 $formatter.Deserialize($stream) } #################################################### function UploadAzureStorageChunk($sasUri, $id, $body) { $uri = "$sasUri&comp=block&blockid=$id" $request = "PUT $uri" $iso = [System.Text.Encoding]::GetEncoding("iso-8859-1") $encodedBody = $iso.GetString($body) $headers = @{ "x-ms-blob-type" = "BlockBlob" } if ($logRequestUris) { Write-Verbose $request } if ($logHeaders) { WriteHeaders $headers } try { Invoke-WebRequest $uri -Method Put -Headers $headers -Body $encodedBody } catch { Write-Error $request Write-Error $_.Exception.Message throw } } #################################################### function FinalizeAzureStorageUpload($sasUri, $ids) { $uri = "$sasUri&comp=blocklist" $request = "PUT $uri" $xml = '<?xml version="1.0" encoding="utf-8"?><BlockList>' foreach ($id in $ids) { $xml += "<Latest>$id</Latest>" } $xml += '</BlockList>' if ($logRequestUris) { Write-Verbose $request } if ($logContent) { Write-Verbose $xml } try { Invoke-RestMethod $uri -Method Put -Body $xml } catch { Write-Error $request Write-Error $_.Exception.Message throw } } #################################################### function UploadFileToAzureStorage($sasUri, $filepath, $fileUri) { try { $chunkSizeInBytes = 1024l * 1024l * $azureStorageUploadChunkSizeInMb # Start the timer for SAS URI renewal. $sasRenewalTimer = [System.Diagnostics.Stopwatch]::StartNew() # Find the file size and open the file. $fileSize = (Get-Item $filepath).length $chunks = [Math]::Ceiling($fileSize / $chunkSizeInBytes) $reader = New-Object System.IO.BinaryReader([System.IO.File]::Open($filepath, [System.IO.FileMode]::Open)) $reader.BaseStream.Seek(0, [System.IO.SeekOrigin]::Begin) # Upload each chunk. Check whether a SAS URI renewal is required after each chunk is uploaded and renew if needed. $ids = @() for ($chunk = 0; $chunk -lt $chunks; $chunk++) { $id = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($chunk.ToString("0000"))) $ids += $id $start = $chunk * $chunkSizeInBytes $length = [Math]::Min($chunkSizeInBytes, $fileSize - $start) $bytes = $reader.ReadBytes($length) $currentChunk = $chunk + 1 Write-Progress -Activity "Uploading File to Azure Storage" -status "Uploading chunk $currentChunk of $chunks" ` -percentComplete ($currentChunk / $chunks * 100) UploadAzureStorageChunk $sasUri $id $bytes # Renew the SAS URI if 7 minutes have elapsed since the upload started or was renewed last. if ($currentChunk -lt $chunks -and $sasRenewalTimer.ElapsedMilliseconds -ge 450000) { RenewAzureStorageUpload $fileUri $sasRenewalTimer.Restart() } } Write-Progress -Completed -Activity "Uploading File to Azure Storage" $reader.Close() } finally { if ($null -ne $reader) { $reader.Dispose() } } # Finalize the upload. FinalizeAzureStorageUpload $sasUri $ids } #################################################### function RenewAzureStorageUpload($fileUri) { $renewalUri = "$fileUri/renewUpload" $actionBody = "" Invoke-MgGraphRequest -method POST -Uri $renewalUri -Body $actionBody Start-WaitForFileProcessing $fileUri "AzureStorageUriRenewal" $azureStorageRenewSasUriBackOffTimeInSeconds } #################################################### function Start-WaitForFileProcessing($fileUri, $stage) { $attempts = 600 $waitTimeInSeconds = 10 $successState = "$($stage)Success" $pendingState = "$($stage)Pending" $file = $null while ($attempts -gt 0) { $file = Invoke-MgGraphRequest -Method GET -Uri $fileUri if ($file.uploadState -eq $successState) { break } elseif ($file.uploadState -ne $pendingState) { Write-Error $_.Exception.Message throw "File upload state is not success: $($file.uploadState)" } Start-Sleep $waitTimeInSeconds $attempts-- } if ($null -eq $file -or $file.uploadState -ne $successState) { throw "File request did not complete in the allotted time." } $file } #################################################### function Get-Win32AppBody() { param ( [parameter(Mandatory = $true, ParameterSetName = "MSI", Position = 1)] [Switch]$MSI, [parameter(Mandatory = $true, ParameterSetName = "EXE", Position = 1)] [Switch]$EXE, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$displayName, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$publisher, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$description, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$filename, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SetupFileName, [parameter(Mandatory = $true)] [ValidateSet('system', 'user')] $installExperience, [parameter(Mandatory = $true, ParameterSetName = "EXE")] [ValidateNotNullOrEmpty()] $installCommandLine, [parameter(Mandatory = $true, ParameterSetName = "EXE")] [ValidateNotNullOrEmpty()] $uninstallCommandLine, [parameter(Mandatory = $true, ParameterSetName = "MSI")] [ValidateNotNullOrEmpty()] $MsiPackageType, [parameter(Mandatory = $true, ParameterSetName = "MSI")] [ValidateNotNullOrEmpty()] $MsiProductCode, [parameter(Mandatory = $false, ParameterSetName = "MSI")] $MsiProductName, [parameter(Mandatory = $true, ParameterSetName = "MSI")] [ValidateNotNullOrEmpty()] $MsiProductVersion, [parameter(Mandatory = $false, ParameterSetName = "MSI")] $MsiPublisher, [parameter(Mandatory = $true, ParameterSetName = "MSI")] [ValidateNotNullOrEmpty()] $MsiRequiresReboot, [parameter(Mandatory = $true, ParameterSetName = "MSI")] [ValidateNotNullOrEmpty()] $MsiUpgradeCode ) if ($MSI) { $body = @{ "@odata.type" = "#microsoft.graph.win32LobApp" } $body.applicableArchitectures = "x64,x86" $body.description = $description $body.developer = "" $body.displayName = $displayName $body.fileName = $filename $body.installCommandLine = "msiexec /i `"$SetupFileName`"" $body.installExperience = @{"runAsAccount" = "$installExperience" } $body.informationUrl = $null $body.isFeatured = $false $body.minimumSupportedOperatingSystem = @{"v10_1607" = $true } $body.msiInformation = @{ "packageType" = "$MsiPackageType" "productCode" = "$MsiProductCode" "productName" = "$MsiProductName" "productVersion" = "$MsiProductVersion" "publisher" = "$MsiPublisher" "requiresReboot" = "$MsiRequiresReboot" "upgradeCode" = "$MsiUpgradeCode" } $body.notes = "" $body.owner = "" $body.privacyInformationUrl = $null $body.publisher = $publisher $body.runAs32bit = $false $body.setupFilePath = $SetupFileName $body.uninstallCommandLine = "msiexec /x `"$MsiProductCode`"" } elseif ($EXE) { $body = @{ "@odata.type" = "#microsoft.graph.win32LobApp" } $body.description = $description $body.developer = "" $body.displayName = $displayName $body.fileName = $filename $body.installCommandLine = "$installCommandLine" $body.installExperience = @{"runAsAccount" = "$installExperience" } $body.informationUrl = $null $body.isFeatured = $false $body.minimumSupportedOperatingSystem = @{"v10_1607" = $true } $body.msiInformation = $null $body.notes = "" $body.owner = "" $body.privacyInformationUrl = $null $body.publisher = $publisher $body.runAs32bit = $false $body.setupFilePath = $SetupFileName $body.uninstallCommandLine = "$uninstallCommandLine" } $body } #################################################### function GetAppFileBody($name, $size, $sizeEncrypted, $manifest) { $body = @{ "@odata.type" = "#microsoft.graph.mobileAppContentFile" } $body.name = $name $body.size = $size $body.sizeEncrypted = $sizeEncrypted $body.manifest = $manifest $body.isDependency = $false $body } #################################################### function GetAppCommitBody($contentVersionId, $LobType) { $body = @{ "@odata.type" = "#$LobType" } $body.committedContentVersion = $contentVersionId $body } #################################################### Function Test-SourceFile() { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] $SourceFile ) try { if (!(test-path "$SourceFile")) { Write-Error "Source File '$sourceFile' doesn't exist..." throw } } catch { Write-Error $_.Exception.Message break } } #################################################### Function New-DetectionRule() { [cmdletbinding()] param ( [parameter(Mandatory = $true, ParameterSetName = "PowerShell", Position = 1)] [Switch]$PowerShell, [parameter(Mandatory = $true, ParameterSetName = "MSI", Position = 1)] [Switch]$MSI, [parameter(Mandatory = $true, ParameterSetName = "File", Position = 1)] [Switch]$File, [parameter(Mandatory = $true, ParameterSetName = "Registry", Position = 1)] [Switch]$Registry, [parameter(Mandatory = $true, ParameterSetName = "PowerShell")] [ValidateNotNullOrEmpty()] [String]$ScriptFile, [parameter(Mandatory = $true, ParameterSetName = "PowerShell")] [ValidateNotNullOrEmpty()] $enforceSignatureCheck, [parameter(Mandatory = $true, ParameterSetName = "PowerShell")] [ValidateNotNullOrEmpty()] $runAs32Bit, [parameter(Mandatory = $true, ParameterSetName = "MSI")] [ValidateNotNullOrEmpty()] [String]$MSIproductCode, [parameter(Mandatory = $true, ParameterSetName = "File")] [ValidateNotNullOrEmpty()] [String]$Path, [parameter(Mandatory = $true, ParameterSetName = "File")] [ValidateNotNullOrEmpty()] [string]$FileOrFolderName, [parameter(Mandatory = $true, ParameterSetName = "File")] [ValidateSet("notConfigured", "exists", "modifiedDate", "createdDate", "version", "sizeInMB")] [string]$FileDetectionType, [parameter(Mandatory = $false, ParameterSetName = "File")] $FileDetectionValue = $null, [parameter(Mandatory = $true, ParameterSetName = "File")] [ValidateSet("True", "False")] [string]$check32BitOn64System = "False", [parameter(Mandatory = $true, ParameterSetName = "Registry")] [ValidateNotNullOrEmpty()] [String]$RegistryKeyPath, [parameter(Mandatory = $true, ParameterSetName = "Registry")] [ValidateSet("notConfigured", "exists", "doesNotExist", "string", "integer", "version")] [string]$RegistryDetectionType, [parameter(Mandatory = $false, ParameterSetName = "Registry")] [ValidateNotNullOrEmpty()] [String]$RegistryValue, [parameter(Mandatory = $true, ParameterSetName = "Registry")] [ValidateSet("True", "False")] [string]$check32BitRegOn64System = "False" ) if ($PowerShell) { if (!(Test-Path "$ScriptFile")) { Write-Error "Could not find file '$ScriptFile'..." Write-Error "Script can't continue..." break } $ScriptContent = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes("$ScriptFile")) $DR = @{ "@odata.type" = "#microsoft.graph.win32LobAppPowerShellScriptDetection" } $DR.enforceSignatureCheck = $false $DR.runAs32Bit = $false $DR.scriptContent = "$ScriptContent" } elseif ($MSI) { $DR = @{ "@odata.type" = "#microsoft.graph.win32LobAppProductCodeDetection" } $DR.productVersionOperator = "notConfigured" $DR.productCode = "$MsiProductCode" $DR.productVersion = $null } elseif ($File) { $DR = @{ "@odata.type" = "#microsoft.graph.win32LobAppFileSystemDetection" } $DR.check32BitOn64System = "$check32BitOn64System" $DR.detectionType = "$FileDetectionType" $DR.detectionValue = $FileDetectionValue $DR.fileOrFolderName = "$FileOrFolderName" $DR.operator = "notConfigured" $DR.path = "$Path" } elseif ($Registry) { $DR = @{ "@odata.type" = "#microsoft.graph.win32LobAppRegistryDetection" } $DR.check32BitOn64System = "$check32BitRegOn64System" $DR.detectionType = "$RegistryDetectionType" $DR.detectionValue = "" $DR.keyPath = "$RegistryKeyPath" $DR.operator = "notConfigured" $DR.valueName = "$RegistryValue" } return $DR } #################################################### function Get-DefaultReturnCodes() { @{"returnCode" = 0; "type" = "success" }, ` @{"returnCode" = 1707; "type" = "success" }, ` @{"returnCode" = 3010; "type" = "softReboot" }, ` @{"returnCode" = 1641; "type" = "hardReboot" }, ` @{"returnCode" = 1618; "type" = "retry" } } #################################################### function New-ReturnCode() { param ( [parameter(Mandatory = $true)] [int]$returnCode, [parameter(Mandatory = $true)] [ValidateSet('success', 'softReboot', 'hardReboot', 'retry')] $type ) @{"returnCode" = $returnCode; "type" = "$type" } } #################################################### Function Get-IntuneWinXML() { param ( [Parameter(Mandatory = $true)] $SourceFile, [Parameter(Mandatory = $true)] $fileName, [Parameter(Mandatory = $false)] [ValidateSet("false", "true")] [string]$removeitem = "true" ) Test-SourceFile "$SourceFile" $Directory = [System.IO.Path]::GetDirectoryName("$SourceFile") Add-Type -Assembly System.IO.Compression.FileSystem $zip = [IO.Compression.ZipFile]::OpenRead("$SourceFile") $zip.Entries | where-object { $_.Name -like "$filename" } | foreach-object { [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, "$Directory\$filename", $true) } $zip.Dispose() [xml]$IntuneWinXML = Get-Content "$Directory\$filename" return $IntuneWinXML if ($removeitem -eq "true") { remove-item "$Directory\$filename" } } #################################################### Function Get-IntuneWinFile() { param ( [Parameter(Mandatory = $true)] $SourceFile, [Parameter(Mandatory = $true)] $fileName, [Parameter(Mandatory = $false)] [string]$Folder = "win32" ) $Directory = [System.IO.Path]::GetDirectoryName("$SourceFile") if (!(Test-Path "$Directory\$folder")) { New-Item -ItemType Directory -Path "$Directory" -Name "$folder" | Out-Null } Add-Type -Assembly System.IO.Compression.FileSystem $zip = [IO.Compression.ZipFile]::OpenRead("$SourceFile") $zip.Entries | Where-Object { $_.Name -like "$filename" } | ForEach-Object { [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, "$Directory\$folder\$filename", $true) } $zip.Dispose() return "$Directory\$folder\$filename" if ($removeitem -eq "true") { remove-item "$Directory\$filename" } } #################################################### function Invoke-UploadWin32Lob() { <# .SYNOPSIS This function is used to upload a Win32 Application to the Intune Service .DESCRIPTION This function is used to upload a Win32 Application to the Intune Service .EXAMPLE Invoke-UploadWin32Lob "C:\Packages\package.intunewin" -publisher "Microsoft" -description "Package" This example uses all parameters required to add an intunewin File into the Intune Service .NOTES NAME: Invoke-UploadWin32Lob #> [cmdletbinding()] param ( [parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [string]$SourceFile, [parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$displayName, [parameter(Mandatory = $true, Position = 2)] [ValidateNotNullOrEmpty()] [string]$publisher, [parameter(Mandatory = $true, Position = 3)] [ValidateNotNullOrEmpty()] [string]$description, [parameter(Mandatory = $true, Position = 4)] [ValidateNotNullOrEmpty()] $detectionRules, [parameter(Mandatory = $true, Position = 5)] [ValidateNotNullOrEmpty()] $returnCodes, [parameter(Mandatory = $false, Position = 6)] [ValidateNotNullOrEmpty()] [string]$installCmdLine, [parameter(Mandatory = $false, Position = 7)] [ValidateNotNullOrEmpty()] [string]$uninstallCmdLine, [parameter(Mandatory = $false, Position = 8)] [ValidateSet('system', 'user')] $installExperience = "system" ) try { $LOBType = "microsoft.graph.win32LobApp" Write-Verbose "Testing if SourceFile '$SourceFile' Path is valid..." Test-SourceFile "$SourceFile" Write-Verbose "Creating JSON data to pass to the service..." # Funciton to read Win32LOB file $DetectionXML = Get-IntuneWinXML "$SourceFile" -fileName "detection.xml" # If displayName input don't use Name from detection.xml file if ($displayName) { $DisplayName = $displayName } else { $DisplayName = $DetectionXML.ApplicationInfo.Name } $FileName = $DetectionXML.ApplicationInfo.FileName $SetupFileName = $DetectionXML.ApplicationInfo.SetupFile $Ext = [System.IO.Path]::GetExtension($SetupFileName) if ((($Ext).contains("msi") -or ($Ext).contains("Msi")) -and (!$installCmdLine -or !$uninstallCmdLine)) { # MSI $MsiExecutionContext = $DetectionXML.ApplicationInfo.MsiInfo.MsiExecutionContext $MsiPackageType = "DualPurpose" if ($MsiExecutionContext -eq "System") { $MsiPackageType = "PerMachine" } elseif ($MsiExecutionContext -eq "User") { $MsiPackageType = "PerUser" } $MsiProductCode = $DetectionXML.ApplicationInfo.MsiInfo.MsiProductCode $MsiProductVersion = $DetectionXML.ApplicationInfo.MsiInfo.MsiProductVersion $MsiPublisher = $DetectionXML.ApplicationInfo.MsiInfo.MsiPublisher $MsiRequiresReboot = $DetectionXML.ApplicationInfo.MsiInfo.MsiRequiresReboot $MsiUpgradeCode = $DetectionXML.ApplicationInfo.MsiInfo.MsiUpgradeCode if ($MsiRequiresReboot -eq "false") { $MsiRequiresReboot = $false } elseif ($MsiRequiresReboot -eq "true") { $MsiRequiresReboot = $true } $mobileAppBody = Get-Win32AppBody ` -MSI ` -displayName "$DisplayName" ` -publisher "$publisher" ` -description $description ` -filename $FileName ` -SetupFileName "$SetupFileName" ` -installExperience $installExperience ` -MsiPackageType $MsiPackageType ` -MsiProductCode $MsiProductCode ` -MsiProductName $displayName ` -MsiProductVersion $MsiProductVersion ` -MsiPublisher $MsiPublisher ` -MsiRequiresReboot $MsiRequiresReboot ` -MsiUpgradeCode $MsiUpgradeCode } else { $mobileAppBody = Get-Win32AppBody -EXE -displayName "$DisplayName" -publisher "$publisher" ` -description $description -filename $FileName -SetupFileName "$SetupFileName" ` -installExperience $installExperience -installCommandLine $installCmdLine ` -uninstallCommandLine $uninstallcmdline } if ($DetectionRules.'@odata.type' -contains "#microsoft.graph.win32LobAppPowerShellScriptDetection" -and @($DetectionRules).'@odata.type'.Count -gt 1) { Write-Warning "A Detection Rule can either be 'Manually configure detection rules' or 'Use a custom detection script'" Write-Warning "It can't include both..." break } else { $mobileAppBody | Add-Member -MemberType NoteProperty -Name 'detectionRules' -Value $detectionRules } #ReturnCodes if ($returnCodes) { $mobileAppBody | Add-Member -MemberType NoteProperty -Name 'returnCodes' -Value @($returnCodes) } else { Write-Warning "Intunewin file requires ReturnCodes to be specified" Write-Warning "If you want to use the default ReturnCode run 'Get-DefaultReturnCodes'" break } Write-Verbose "Creating application in Intune..." $mobileApp = New-MgDeviceAppMgtMobileApp -BodyParameter ($mobileAppBody | ConvertTo-Json) # Get the content version for the new app (this will always be 1 until the new app is committed). Write-Verbose "Creating Content Version in the service for the application..." $appId = $mobileApp.id $contentVersionUri = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$appId/$LOBType/contentVersions" $contentVersion = Invoke-MgGraphRequest -method POST -Uri $contentVersionUri -Body "{}" # Encrypt file and Get File Information Write-Verbose "Getting Encryption Information for '$SourceFile'..." $encryptionInfo = @{} $encryptionInfo.encryptionKey = $DetectionXML.ApplicationInfo.EncryptionInfo.EncryptionKey $encryptionInfo.macKey = $DetectionXML.ApplicationInfo.EncryptionInfo.macKey $encryptionInfo.initializationVector = $DetectionXML.ApplicationInfo.EncryptionInfo.initializationVector $encryptionInfo.mac = $DetectionXML.ApplicationInfo.EncryptionInfo.mac $encryptionInfo.profileIdentifier = "ProfileVersion1" $encryptionInfo.fileDigest = $DetectionXML.ApplicationInfo.EncryptionInfo.fileDigest $encryptionInfo.fileDigestAlgorithm = $DetectionXML.ApplicationInfo.EncryptionInfo.fileDigestAlgorithm $fileEncryptionInfo = @{} $fileEncryptionInfo.fileEncryptionInfo = $encryptionInfo # Extracting encrypted file $IntuneWinFile = Get-IntuneWinFile "$SourceFile" -fileName "$filename" [int64]$Size = $DetectionXML.ApplicationInfo.UnencryptedContentSize $EncrySize = (Get-Item "$IntuneWinFile").Length # Create a new file for the app. Write-Verbose "Creating a new file entry in Azure for the upload..." $contentVersionId = $contentVersion.id $fileBody = GetAppFileBody "$FileName" $Size $EncrySize $null $filesUri = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$appId/$LOBType/contentVersions/$contentVersionId/files" $file = Invoke-MgGraphRequest -Method POST -Uri $filesUri -Body ($fileBody | ConvertTo-Json) # Wait for the service to process the new file request. Write-Verbose "Waiting for the file entry URI to be created..." $fileId = $file.id $fileUri = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$appId/$LOBType/contentVersions/$contentVersionId/files/$fileId" $file = Start-WaitForFileProcessing $fileUri "AzureStorageUriRequest" # Upload the content to Azure Storage. Write-Verbose "Uploading file to Azure Storage..." UploadFileToAzureStorage $file.azureStorageUri "$IntuneWinFile" $fileUri # Need to Add removal of IntuneWin file Remove-Item "$IntuneWinFile" -Force # Commit the file. Write-Verbose "Committing the file into Azure Storage..." $commitFileUri = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$appId/$LOBType/contentVersions/$contentVersionId/files/$fileId/commit" Invoke-MgGraphRequest -Uri $commitFileUri -Method POST -Body ($fileEncryptionInfo | ConvertTo-Json) # Wait for the service to process the commit file request. Write-Verbose "Waiting for the service to process the commit file request..." $file = Start-WaitForFileProcessing $fileUri "CommitFile" # Commit the app. Write-Verbose "Committing the file into Azure Storage..." $commitAppUri = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$appId" $commitAppBody = GetAppCommitBody $contentVersionId $LOBType Invoke-MgGraphRequest -Method PATCH -Uri $commitAppUri -Body ($commitAppBody | ConvertTo-Json) foreach ($i in 0..$sleep) { Write-Progress -Activity "Sleeping for $($sleep-$i) seconds" -PercentComplete ($i / $sleep * 100) -SecondsRemaining ($sleep - $i) Start-Sleep -s 1 } } catch { Write-Error "Aborting with exception: $($_.Exception.ToString())" } } $logRequestUris = $true $logHeaders = $false $logContent = $true $azureStorageUploadChunkSizeInMb = 6l $sleep = 30 Function Get-IntuneApplication() { <# .SYNOPSIS This function is used to get applications from the Graph API REST interface .DESCRIPTION The function connects to the Graph API Interface and gets any applications added .EXAMPLE Get-IntuneApplication Returns any applications configured in Intune .NOTES NAME: Get-IntuneApplication #> try { return Get-MgDeviceAppMgtMobileApp -All | Where-Object { (!($_.AdditionalProperties['@odata.type']).Contains("managed")) } } catch { $ex = $_.Exception Write-Verbose "Request to $Uri failed with HTTP Status $([int]$ex.Response.StatusCode) $($ex.Response.StatusDescription)" $errorResponse = $ex.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($errorResponse) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $responseBody = $reader.ReadToEnd(); Write-Verbose "Response content:`n$responseBody" Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" break } } function new-aadgroups { [cmdletbinding()] param ( $appid, $appname, $grouptype ) switch ($grouptype) { "install" { $groupname = $appname + " Install Group" $nickname = $appid + "install" $groupdescription = "Group for installation and updating of $appname application" } "uninstall" { $groupname = $appname + " Uninstall Group" $nickname = $appid + "uninstall" $groupdescription = "Group for uninstallation of $appname application" } } $grp = New-MgGroup -DisplayName $groupname -Description $groupdescription -MailEnabled:$False -MailNickName $nickname -SecurityEnabled:$True return $grp.id } function new-intunewinfile { param ( $appid, $appname, $apppath, $setupfilename ) . $intuneapputiloutput -c "$apppath" -s "$setupfilename" -o "$apppath" -q } function new-detectionscriptinstall { param ( $appid, $appname ) $detection = @" `$File = "C:\Program Files\Remote Help\RemoteHelp.exe" if (Test-Path `$File) { write-output "RemoteHelp detected, exiting" exit 0 } else { exit 1 } "@ return $detection } function new-installscript { param ( $appid, $appname ) $install = @" &.\remotehelpinstaller.exe /quiet acceptTerms=1 "@ return $install } function new-uninstallscript { param ( $appid, $appname ) $uninstall = @" &.\remotehelpinstaller.exe /uninstall /quiet acceptTerms=1 "@ return $uninstall } function grant-win32app { param ( $appname, $installgroup, $uninstallgroup ) $Application = Get-IntuneApplication | where-object { $_.displayName -eq "$appname" -and $_.description -like "*Winget*" } #Install $ApplicationId = $Application.id $TargetGroupId1 = $installgroup $InstallIntent1 = "required" #Uninstall $ApplicationId = $Application.id $TargetGroupId = $uninstallgroup $InstallIntent = "uninstall" $JSON = @" { "mobileAppAssignments": [ { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.groupAssignmentTarget", "groupId": "$TargetGroupId1" }, "intent": "$InstallIntent1" }, { "@odata.type": "#microsoft.graph.mobileAppAssignment", "target": { "@odata.type": "#microsoft.graph.groupAssignmentTarget", "groupId": "$TargetGroupId" }, "intent": "$InstallIntent" } ] } "@ Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$ApplicationId/assign" -Method POST -Body $JSON } function new-win32app { [cmdletbinding()] param ( $appid, $appname, $appfile, $installcmd, $uninstallcmd, $detectionfile ) # Defining Intunewin32 detectionRules $PSRule = New-DetectionRule -PowerShell -ScriptFile $detectionfile -enforceSignatureCheck $false -runAs32Bit $false # Creating Array for detection Rule $DetectionRule = @($PSRule) $ReturnCodes = Get-DefaultReturnCodes # Win32 Application Upload $appupload = Invoke-UploadWin32Lob -SourceFile "$appfile" -DisplayName "$appname" -publisher "Winget" ` -description "$appname Winget Package" -detectionRules $DetectionRule -returnCodes $ReturnCodes ` -installCmdLine "$installcmd" ` -uninstallCmdLine "$uninstallcmd" return $appupload } Function Connect-ToGraph { <# .SYNOPSIS Authenticates to the Graph API via the Microsoft.Graph.Authentication module. .DESCRIPTION The Connect-ToGraph cmdlet is a wrapper cmdlet that helps authenticate to the Intune Graph API using the Microsoft.Graph.Authentication module. It leverages an Azure AD app ID and app secret for authentication or user-based auth. .PARAMETER Tenant Specifies the tenant (e.g. contoso.onmicrosoft.com) to which to authenticate. .PARAMETER AppId Specifies the Azure AD app ID (GUID) for the application that will be used to authenticate. .PARAMETER AppSecret Specifies the Azure AD app secret corresponding to the app ID that will be used to authenticate. .PARAMETER Scopes Specifies the user scopes for interactive authentication. .EXAMPLE Connect-ToGraph -TenantId $tenantID -AppId $app -AppSecret $secret -#> [cmdletbinding()] param ( [Parameter(Mandatory = $false)] [string]$Tenant, [Parameter(Mandatory = $false)] [string]$AppId, [Parameter(Mandatory = $false)] [string]$AppSecret, [Parameter(Mandatory = $false)] [string]$scopes ) Process { Import-Module Microsoft.Graph.Authentication $version = (get-module microsoft.graph.authentication | Select-Object -expandproperty Version).major if ($AppId -ne "") { $body = @{ grant_type = "client_credentials"; client_id = $AppId; client_secret = $AppSecret; scope = "https://graph.microsoft.com/.default"; } $response = Invoke-RestMethod -Method Post -Uri https://login.microsoftonline.com/$Tenant/oauth2/v2.0/token -Body $body $accessToken = $response.access_token $accessToken if ($version -eq 2) { write-host "Version 2 module detected" $accesstokenfinal = ConvertTo-SecureString -String $accessToken -AsPlainText -Force } else { write-host "Version 1 Module Detected" Select-MgProfile -Name Beta $accesstokenfinal = $accessToken } $graph = Connect-MgGraph -AccessToken $accesstokenfinal Write-Host "Connected to Intune tenant $TenantId using app-based authentication (Azure AD authentication not supported)" } else { if ($version -eq 2) { write-host "Version 2 module detected" } else { write-host "Version 1 Module Detected" Select-MgProfile -Name Beta } $graph = Connect-MgGraph -scopes $scopes Write-Host "Connected to Intune tenant $($graph.TenantId)" } } } ############################################################################################################ ###### END FUNCTIONS SECTION ######## ############################################################################################################ ##Connect to Graph Write-Verbose "Connecting to Microsoft Graph" Connect-ToGraph -Scopes "DeviceManagementApps.ReadWrite.All, DeviceManagementConfiguration.ReadWrite.All, DeviceManagementServiceConfig.ReadWrite.All, Group.ReadWrite.All, GroupMember.ReadWrite.All, openid, profile, email, offline_access" Write-Verbose "Graph connection established" ##Enable Remote Help $enableuri = "https://graph.microsoft.com/beta/deviceManagement/remoteAssistanceSettings" $json = @" { "allowSessionsToUnenrolledDevices": true, "blockChat": false, "remoteAssistanceState": "enabled" } "@ Invoke-MgGraphRequest -Method PATCH -Uri $enableuri -Body $json $appname = "Remote Help" $appid = "RemoteHelp" ##Create Directory Write-Verbose "Creating Directory for $appname" $apppath = "$path\$appid" new-item -Path $apppath -ItemType Directory -Force Write-Host "Directory $apppath Created" ##RemoteHelp ##Download the app $remoteapputilurl = "https://aka.ms/downloadremotehelp" $remoteapputiloutput = $apppath + "\remotehelpinstaller.exe" Invoke-WebRequest -Uri $remoteapputilurl -OutFile $remoteapputiloutput ##Create Groups Write-Verbose "Creating AAD Groups for $appname" $installgroup = new-aadgroups -appid $appid -appname $appname -grouptype "Install" $uninstallgroup = new-aadgroups -appid $appid -appname $appname -grouptype "Uninstall" Write-Host "Created $installgroup for installing $appname" Write-Host "Created $uninstallgroup for uninstalling $appname" ##Create Install Script Write-Verbose "Creating Install Script for $appname" $installscript = new-installscript -appid $appid -appname $appname $installfilename = "install$appid.ps1" $installscriptfile = $apppath + "\" + $installfilename $installscript | Out-File $installscriptfile -Encoding utf8 Write-Host "Script created at $installscriptfile" ##Create Uninstall Script Write-Verbose "Creating Uninstall Script for $appname" $uninstallscript = new-uninstallscript -appid $appid -appname $appname $uninstallfilename = "uninstall$appid.ps1" $uninstallscriptfile = $apppath + "\" + $uninstallfilename $uninstallscript | Out-File $uninstallscriptfile -Encoding utf8 Write-Host "Script created at $uninstallscriptfile" ##Create Detection Script Write-Verbose "Creating Detection Script for $appname" $detectionscript = new-detectionscriptinstall -appid $appid -appname $appname $detectionscriptfile = $apppath + "\detection$appid.ps1" $detectionscript | Out-File $detectionscriptfile -Encoding utf8 Write-Host "Script created at $detectionscriptfile" ##Create IntuneWin Write-Verbose "Creating Intunewin File for $appname" $intunewinpath = $apppath + "\install$appid.intunewin" new-intunewinfile -appid "$appid" -appname "$appname" -apppath "$apppath" -setupfilename "$installscriptfile" Write-Host "Intunewin $intunewinpath Created" $sleep = 10 foreach ($i in 0..$sleep) { Write-Progress -Activity "Sleeping for $($sleep-$i) seconds" -PercentComplete ($i / $sleep * 100) -SecondsRemaining ($sleep - $i) Start-Sleep -s 1 } ##Create and upload Win32 Write-Verbose "Uploading $appname to Intune" $installcmd = "powershell.exe -ExecutionPolicy Bypass -File $installfilename" $uninstallcmd = "powershell.exe -ExecutionPolicy Bypass -File $uninstallfilename" new-win32app -appid $appid -appname $appname -appfile $intunewinpath -installcmd $installcmd -uninstallcmd $uninstallcmd -detectionfile $detectionscriptfile Write-Host "$appname Created and uploaded" ##Assign Win32 Write-Verbose "Assigning Groups" grant-win32app -appname $appname -installgroup $installgroup -uninstallgroup $uninstallgroup Write-Host "Assigned $installgroup as Required Install to $appname" Write-Host "Assigned $uninstallgroup as Required Uninstall to $appname" ##Done Write-Host "$appname packaged and deployed" Stop-Transcript |