This commit is contained in:
jamel@microsoft.com 2023-02-17 08:37:08 -08:00
Родитель 9b571f9238
Коммит d0ec65ea07
11 изменённых файлов: 1472 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,39 @@
param
(
    [Parameter(Mandatory=$true)]
    $EscapedSASToken,
    [Parameter(Mandatory=$true)]
    $StorageURL,
    [Parameter(Mandatory=$true)]
    $FilesOrFolders
) 
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$SASToken =  [uri]::UnescapeDataString($EscapedSASToken) 
$FilesOrFoldersColletion =  $FilesOrFolders.Split(',', [StringSplitOptions]::RemoveEmptyEntries)
foreach ($FileOrFolder in $FilesOrFoldersColletion)
{
    if ((Test-Path -Path $FileOrFolder) -eq $false)
    {
        Write-Warning "'$FileOrFolders' does not exist"
        continue
    }
    $Headers = @{
        'x-ms-blob-type' = 'BlockBlob'
    }
    $FilesToUpload = Get-ChildItem -Path $FileOrFolder -Recurse |  Where-Object { ! $_.PSIsContainer } | %{$_.FullName}
    foreach ($File in $FilesToUpload)
    {
        $Uri = "{0}/$env:COMPUTERNAME/{1}{2}" -f $StorageURL, $File , $SASToken
        try 
        {
           Invoke-RestMethod  -Uri $Uri -Method Put -Headers $Headers -InFile $File | Out-Null
        }
        catch 
        {
            #file is being used by another process.
        }
    }
    Write-Output "File(s) Uploaded"
}

Просмотреть файл

@ -0,0 +1,295 @@
<#
.SYNOPSIS
Colect Device Logs from a Collection ID or Device Name in SCCM
.DESCRIPTION
Powershell script used to collect device logs from user devices from SCCM.
It works by connecting to SCCM, running a Powershell script called Collect Logs to Cloud
stored in SCCM on each device collecting specific logs, connecting to an Azure Storage
Account with an MSI using Client Id, and then sending the logs to the Storage Account. An
HTML Report is also generated in the local filepath with the results.
.PARAMETER SiteCode
SCCM Site Code
.PARAMETER SiteServer
SCCM Site Server
.PARAMETER StorageAccountName
Name of Azure Storage Account
.PARAMETER StorageAccountResourceGroup
Name of Azure Storage Account Resource Group
.PARAMETER Container
Name of Container in Azure Storage Account
.PARAMETER ClientId
Azure MSI Client Id used to get the Storage Account Access Token
.NOTES
Version: 1.0
Creation date: 02/09/2023
Purpose/Change: Open Source example
#>
$ErrorActionPreference = "Stop"
$SiteCode = "" #Enter SCCM Site Code
$SiteServer = "" #Enter SCCM Site Server Fully Qualified Domain Name
$Namespace = "root\sms\site_$SiteCode"
$StorageAccountName = "" #Enter Azure Storage Account Name where logs will be sent
$StorageAccountResourceGroup = "" #Enter Azure Storage Account Resource Group Name where logs will be sent
$Container = "" #Enter Azure Storage Account Container Name where logs will be sent
$StorageURL = "https://$($StorageAccountName).blob.core.windows.net/$($Container)"
enum EnumExecutionState
{
Succeeded = 1
Failed = 2
}
function Create-HTMLTable
{
param(
[Parameter(Mandatory)]
$TableData
)
$CSS = "<style>
TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH{border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
TD{border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
.odd { background-color:#ffffff;}
.even { background-color:#e7e7e7;}
</style>"
return ($TableData | ConvertTo-HTML -as Table -Fragment -PreContent $CSS | Out-String)
}
function Connect-ConfigMgrProvider($SiteCode, $SiteServer)
{
$initParams = @{}
$Module = (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName + "\ConfigurationManager.psd1"
Import-Module $Module @initParams
if ((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $SiteServer -Scope Script @initParams | Out-Null
}
Set-Location "$($SiteCode):\" @initParams | Out-Null
}
function Get-MsiAccessToken
{
param
(
[Parameter(Mandatory=$true)]
[Validateset("https://graph.microsoft.com","https://vault.azure.net","https://management.azure.com","https://database.windows.net")]
$ResourceUri,
[Parameter(Mandatory=$true)]
$ClientId
)
$msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=$($ResourceUri)&client_id=$($ClientId)"
$response = Invoke-WebRequest -Uri $msiEndpoint -Headers @{"Metadata"= $true} -UseBasicParsing
$responseJson = ConvertFrom-Json -InputObject $response
$AccessToken = $responseJson.access_token
return $AccessToken
}
function Get-SASToken($StorageAccountName, $StorageAccountResourceGroup)
{
$ClientId = "" #Enter MSI Client Id with access to the Azure Storage Account
$AccessToken = Get-MsiAccessToken -ResourceUri https://management.azure.com -ClientId $ClientId
Connect-AzAccount -AccessToken $AccessToken -AccountId $ManagedIdentity | Out-Null
$StorageAccountAccessKey = (Get-AzStorageAccountKey -ResourceGroupName $StorageAccountResourceGroup -Name $StorageAccountName).Value[0]
$StorageContext = New-AzStorageContext $StorageAccountName -StorageAccountKey $StorageAccountAccessKey
New-AzStorageAccountSASToken -Context $StorageContext -Service Blob -ResourceType Object -Permission "rw" -ExpiryTime (Get-Date).AddHours(1)
}
function WaitToComplete($OperationId)
{
Write-Output "OperationId - $($OperationId) - checking Runscript execution status"
#Get script output, wait a maximum of 30 minutes
$pollTimeOut = (Get-Date).AddMinutes(30)
do
{
$ExecutionStatuses = Get-CimInstance -ComputerName $SiteServer -Namespace $Namespace -ClassName SMS_ScriptsExecutionStatus -Filter "ClientOperationID = '$OperationId'"
if ($ExecutionStatuses)
{
Write-Output "RunScript Execution Completed : OperationId - $($OperationId)"
return $ExecutionStatuses
}
Write-Output "OperationId - $($OperationId) - Sleeping for 15 seconds for check Runscript execution status"
Start-Sleep -Seconds 15
}
while ((Get-date) -le $pollTimeOut)
Write-Output "OperationId - $($OperationId) - Get Runscript execution status timedout"
}
function Collect-Log ($TargetSources, $FilesOrFolders)
{
Begin
{
Write-Output "Executing Collect-Logs script"
Write-Output "TargetSources : $TargetSources, FilesOrFolders : $FilesOrFolders"
$TargetSourcesColletion = $TargetSources.Split(',', [StringSplitOptions]::RemoveEmptyEntries)
$FilesOrFoldersColletion = $FilesOrFolders.Split(',', [StringSplitOptions]::RemoveEmptyEntries).ToLowerInvariant()
$AllowedFolders = 'c:\windows\ccm\logs,c:\windows\ccmsetup\logs'
foreach ($Folder in $FilesOrFoldersColletion)
{
if ($AllowedFolders.Contains($Folder) -eq $false)
{
Write-Error "Folder : '$Folder' not in scope to collect device log"
break
}
}
#Connect to SCCM
Connect-ConfigMgrProvider -SiteCode $SiteCode -SiteServer $SiteServer
$TargetCollections = @()
$TargetDevices = @()
foreach ($TargetSource in $TargetSourcesColletion)
{
$Collection = Get-CMCollection -CollectionId $TargetSource
if ([string]::IsNullOrEmpty($Collection))
{
$Device = Get-CMDevice -Name $TargetSource -Fast
if ([string]::IsNullOrEmpty($Device))
{
Write-Error "'$TargetSource' is not valid Device or Collection"
}
else
{
$TargetDevices += $Device
}
}
else
{
$TargetCollections += $TargetSource
}
}
$SASToken = Get-SASToken -StorageAccountName $StorageAccountName -StorageAccountResourceGroup $StorageAccountResourceGroup
$RunScriptName = "Collect Logs to Cloud" #This file needs to be uploaded to SCCM
Write-Output "Getting CM Script '$RunScriptName' from ConfigManager"
$ScriptGuid = (Get-CMScript -ScriptName $RunScriptName -Fast).ScriptGuid
if ([string]::IsNullOrEmpty($ScriptGuid))
{
Write-Error "No Script found with the name '$RunScriptName' in the ConfigMgr Script Repository"
break
}
}
Process
{
$EscapedSASToken = [uri]::EscapeDataString($SASToken)
$Parameters = @{
"EscapedSASToken" = "$EscapedSASToken"
"StorageURL" = "$StorageURL"
"FilesOrFolders" = "$FilesOrFolders"
}
$ExecutionResults = @()
$CollectionOperationResults = @()
$DeviceOperationResults = @()
$CollectionExecutionStatuses = @()
$DeviceExecutionStatuses = @()
foreach ($TargetCollection in $TargetCollections)
{
Write-Output "Invoking Runscript on Config Manager for Collection $($TargetCollection)"
$OperationId = (Invoke-CMScript -CollectionId $TargetCollection -ScriptGuid $ScriptGuid -ScriptParameter $Parameters -PassThru).OperationID
Write-Output "Runscript Invoked on Config Manager for Collection $($TargetCollection) with OperationId: $($OperationId)"
$Record = New-Object PSObject
$Record | Add-Member -type NoteProperty -Name 'Collection' -Value $TargetCollection
$Record | Add-Member -type NoteProperty -Name 'OperationId' -Value $OperationId
$CollectionOperationResults += $Record
}
foreach ($TargetDevice in $TargetDevices)
{
Write-Output "Invoking Runscript on Config Manager for Device $($TargetDevice.Name)"
$OperationId = (Invoke-CMScript -Device $TargetDevice -ScriptGuid $ScriptGuid -ScriptParameter $Parameters -PassThru).OperationID
Write-Output "Runscript Invoked on Config Manager for Device $($TargetDevice.Name) with OperationId: $($OperationId)"
$Record = New-Object PSObject
$Record | Add-Member -type NoteProperty -Name 'Device' -Value $TargetDevice.Name
$Record | Add-Member -type NoteProperty -Name 'OperationId' -Value $OperationId
$DeviceOperationResults += $Record
}
# Check execution status
foreach ($DeviceOperation in $DeviceOperationResults)
{
$ExecutionStatuses = WaitToComplete -OperationId $DeviceOperation.OperationId
$Record = New-Object PSObject
$Record | Add-Member -type NoteProperty -Name 'Device' -Value $DeviceOperation.Device
$Record | Add-Member -type NoteProperty -Name 'OperationId' -Value $DeviceOperation.OperationId
$Record | Add-Member -type NoteProperty -Name 'ExecutionStatuses' -Value $ExecutionStatuses
$DeviceExecutionStatuses += $Record
}
foreach ($CollectionOperation in $CollectionOperationResults)
{
$ExecutionStatuses = WaitToComplete -OperationId $CollectionOperation.OperationId
$Record = New-Object PSObject
$Record | Add-Member -type NoteProperty -Name 'Collection' -Value $CollectionOperation.Collection
$Record | Add-Member -type NoteProperty -Name 'OperationId' -Value $CollectionOperation.OperationId
$Record | Add-Member -type NoteProperty -Name 'ExecutionStatuses' -Value $ExecutionStatuses
$CollectionExecutionStatuses += $Record
}
$Body = "RunScript : <b>$RunScriptName</b>, Target Source : $TargetSources"
#Prepare report for Collection
foreach ($CollectionExecutionStatus in $CollectionExecutionStatuses)
{
$ExecutionResults = @()
Write-Output "Fetching collection members"
$CollectionMembers = (Get-CMCollectionMember -CollectionId $CollectionExecutionStatus.Collection).Name
Write-Output "Checking Runscript execution status for each members for collection : $($CollectionExecutionStatus.Collection)"
foreach ($Member in $CollectionMembers)
{
$ExecutionStatus = $CollectionExecutionStatus.ExecutionStatuses | Where-Object { $_.DeviceName -eq $Member }
if ($ExecutionStatus -ne $null)
{
$ExecutionResults += [PSCustomObject] @{DeviceName = $($ExecutionStatus).DeviceName
ExecutionState = ([EnumExecutionState]$($ExecutionStatus).ScriptExecutionState).ToString()
ExitCode = $($ExecutionStatus).ScriptExitCode
ScriptOutput = $($ExecutionStatus).ScriptOutput
}
}
else
{
$ExecutionResults += [PSCustomObject] @{DeviceName = $Member
ExecutionState = 'Unknown'
ExitCode = 'Unknown'
ScriptOutput = 'NA'
}
}
}
$ExecTask = Get-CimInstance -ComputerName $SiteServer -Namespace $Namespace -ClassName SMS_ScriptsExecutionTask -Filter "ClientOperationID = '$($CollectionExecutionStatus.OperationId)'"
$OverAllResult = [PSCustomObject] @{Completed = $($ExecTask.CompletedClients)
NotApplicable = $($ExecTask.NotApplicableClients)
Failed = $($ExecTask.FailedClients)
Unknown = $($ExecTask.UnknownClients)
Offline = $($ExecTask.OfflineClients)
}
$OverAllResultTable = Create-HTMLTable -TableData $OverAllResult
$ExecutionResultTable = Create-HTMLTable -TableData $ExecutionResults
$Body += "<br><br><b>Overall Script Execution Status : Collection <em>$($CollectionExecutionStatus.Collection)</em></b><br><br>"
$Body += $OverAllResultTable
$Body += "<br><br><b>Client Execution Status : Collection <em>$($CollectionExecutionStatus.Collection)</em></b><br><br>"
$Body += $ExecutionResultTable
}
#Prepare report for Devices
foreach ($DeviceExecutionStatus in $DeviceExecutionStatuses)
{
$ExecutionResults = @()
$ExecutionStatus = $DeviceExecutionStatus.ExecutionStatuses
if ($ExecutionStatus -ne $null)
{
$ExecutionResults += [PSCustomObject] @{DeviceName = $($ExecutionStatus).DeviceName
ExecutionState = ([EnumExecutionState]$($ExecutionStatus).ScriptExecutionState).ToString()
ExitCode = $($ExecutionStatus).ScriptExitCode
ScriptOutput = $($ExecutionStatus).ScriptOutput
}
}
else
{
$ExecutionResults += [PSCustomObject] @{DeviceName = $Member
ExecutionState = 'Unknown'
ExitCode = 'Unknown'
ScriptOutput = 'NA'
}
}
$ExecutionResultTable = Create-HTMLTable -TableData $ExecutionResults
$Body += "<br><br><b>Individual Device Execution Status</b><br><br>"
$Body += $ExecutionResultTable
}
$Body += "<br><br>Files Uploaded to Storage Account : $($StorageURL)"
Write-Output "Collect-Logs Script Execution Completed"
$Body | ConvertTo-Html | Out-File -FilePath .\Collect-DeviceLogs-Report.html
}
End
{
Get-Content $global:LogFileName -ErrorAction SilentlyContinue
Remove-Item -Path $global:LogFileName -Force -ErrorAction SilentlyContinue
}
}
$TargetSources = "" #Comma separated list of SCCM Collection Ids or Device Names to collect logs from
$FilesOrFolders = "" #Comma separated list of Files/Folders Path to collect the logs from
Collect-Log -TargetSources $TargetSources -FilesOrFolders $FilesOrFolders

Двоичные данные
CollectSCCMDeviceLogsAutomation/docs/CollectDeviceLogs.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 20 KiB

Просмотреть файл

@ -0,0 +1,26 @@
# SCCM Collect Device Logs Automation
Powershell script used to collect device logs from user devices from SCCM. It works by connecting to SCCM, running a Powershell script called Collect Logs to Cloud stored in SCCM on each device collecting specific logs, connecting to an Azure Storage Account with an MSI using Client Id, and then sending the logs to the Storage Account. An HTML Report is also generated in the local filepath with the results.
## Prerequisites
- SCCM script "Collect Logs to Cloud" must be uploaded to SCCM
- Az Powershell module installed
- Azure Storage Account set up with MSI Access
- Powershell ISE
### Steps:
1. Open PowerShell ISE in elevated mode and open the following script: Collect-DeviceLogs.ps1
2. Manually configure the following variables for your own environment:
- SiteCode: SCCM Site Code
- SiteServer: SCCM Site Server
- StorageAccountName: Name of Azure Storage Account
- StorageAccountResourceGroup: Name of Azure Storage Account Resource Group
- Container: Name of Container in Azure Storage Account
- ClientId: Azure MSI Client Id used to get the Storage Account Access Token
3. Run the Powershell script
## Notes
The SCCM Collect Device Logs Automation script was originally created for use inside of Microsoft. We have modified it to be more generic, so it can be used as a template for other SCCM environments outside of Microsoft.

Просмотреть файл

@ -0,0 +1,898 @@
<#
.SYNOPSIS
Provision Win 32 App to be uploaded to Intune
.DESCRIPTION
Powershell script used to upload a win32 app into Intune.
It works by getting a Graph API token using an MSI Client Id
and then uploads the intunewin file into Intune
.PARAMETER ClientId
Azure MSI Client Id used to get the Graph API access token
.PARAMETER SourceFile
This is the path to the Intunewin file
.PARAMETER Publisher
The publisher of the application
.PARAMETER Description
Description of the application
.PARAMETER PowerShellDetectionScript
This is the path to the detection script
.NOTES
Version: 1.0
Creation date: 2/15/2023
Purpose/Change: Open Source example
#>
function Get-MsiAuthHeaders
{
param
(
[Parameter(Mandatory=$true)]
[Validateset("https://graph.microsoft.com","https://vault.azure.net","https://management.azure.com","https://database.windows.net")]
$ResourceUri,
[Parameter(Mandatory=$true)]
$ClientId
)
$msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=$($ResourceUri)&client_id=$($ClientId)"
$response = Invoke-WebRequest -Uri $msiEndpoint -Headers @{"Metadata"= $true} -UseBasicParsing
$responseJson = ConvertFrom-Json -InputObject $response
$AccessToken = $responseJson.access_token
$Headers = @{
'Content-Type'='application/json'
'Authorization'="Bearer " + $AccessToken
}
return $Headers
}
 
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 WriteHeaders($headers)
{
foreach ($header in $headers.GetEnumerator())
{
if ($header.Name.ToLower() -eq "authorization")
{
continue;
}
Write-Host -ForegroundColor Gray "$($header.Name): $($header.Value)";
}
}
function MakeGetRequest($collectionPath)
{
$uri = "$baseUrl$collectionPath";
$request = "GET $uri";
if ($logRequestUris) { Write-Host $request; }
if ($logHeaders) { WriteHeaders $headers; }
try
{
$response = Invoke-RestMethod $uri -Method Get -Headers $headers;
$response;
}
catch
{
Write-Host -ForegroundColor Red $request;
Write-Host -ForegroundColor Red $_.Exception.Message;
throw;
}
}
function MakePatchRequest($collectionPath, $body)
{
MakeRequest "PATCH" $collectionPath $body;
}
function MakePostRequest($collectionPath, $body)
{
MakeRequest "POST" $collectionPath $body;
}
function MakeRequest($verb, $collectionPath, $body)
{
$uri = "$baseUrl$collectionPath";
$request = "$verb $uri";
$clonedHeaders = CloneObject $headers;
$clonedHeaders["content-length"] = $body.Length;
$clonedHeaders["content-type"] = "application/json";
if ($logRequestUris) { Write-Host $request; }
if ($logHeaders) { WriteHeaders $clonedHeaders; }
if ($logContent) { Write-Host -ForegroundColor Gray $body; }
try
{
$response = Invoke-RestMethod $uri -Method $verb -Headers $clonedHeaders -Body $body;
$response;
}
catch
{
Write-Host -ForegroundColor Red $request;
Write-Host -ForegroundColor Red $_.Exception.Message;
throw;
}
}
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-Host $request; }
if ($logHeaders) { WriteHeaders $headers; }
try
{
$response = Invoke-WebRequest $uri -Method Put -Headers $headers -Body $encodedBody;
}
catch
{
Write-Host -ForegroundColor Red $request;
Write-Host -ForegroundColor Red $_.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-Host $request; }
if ($logContent) { Write-Host -ForegroundColor Gray $xml; }
try
{
Invoke-RestMethod $uri -Method Put -Body $xml;
}
catch
{
Write-Host -ForegroundColor Red $request;
Write-Host -ForegroundColor Red $_.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));
$position = $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)
$uploadResponse = 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){
$renewalResponse = RenewAzureStorageUpload $fileUri;
$sasRenewalTimer.Restart();
}
}
Write-Progress -Completed -Activity "Uploading File to Azure Storage"
$reader.Close();
}
finally {
if ($reader -ne $null) { $reader.Dispose(); }
}
# Finalize the upload.
$uploadResponse = FinalizeAzureStorageUpload $sasUri $ids;
}
function RenewAzureStorageUpload($fileUri)
{
$renewalUri = "$fileUri/renewUpload";
$actionBody = "";
$rewnewUriResult = MakePostRequest $renewalUri $actionBody;
$file = WaitForFileProcessing $fileUri "AzureStorageUriRenewal" $azureStorageRenewSasUriBackOffTimeInSeconds;
}
function WaitForFileProcessing($fileUri, $stage)
{
$attempts= 60;
$waitTimeInSeconds = 1;
$successState = "$($stage)Success";
$pendingState = "$($stage)Pending";
$failedState = "$($stage)Failed";
$timedOutState = "$($stage)TimedOut";
$file = $null;
while ($attempts -gt 0)
{
$file = MakeGetRequest $fileUri;
if ($file.uploadState -eq $successState)
{
break;
}
elseif ($file.uploadState -ne $pendingState)
{
Write-Host -ForegroundColor Red $_.Exception.Message;
throw "File upload state is not success: $($file.uploadState)";
}
Start-Sleep $waitTimeInSeconds;
$attempts--;
}
if ($file -eq $null -or $file.uploadState -ne $successState)
{
throw "File request did not complete in the allotted time.";
}
$file;
}
function GetWin32AppBody()
{
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 = "system",
[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-Host "Source File '$sourceFile' doesn't exist..." -ForegroundColor Red
throw
}
}
catch {
Write-Host -ForegroundColor Red $_.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-Host "Could not find file '$ScriptFile'..." -ForegroundColor Red
Write-Host "Script can't continue..." -ForegroundColor Red
Write-Host
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 {$_.Name -like "$filename" } | foreach {
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, "$Directory\$filename", $true)
}
$zip.Dispose()
[xml]$IntuneWinXML = gc "$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 {$_.Name -like "$filename" } | foreach {
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, "$Directory\$folder\$filename", $true)
}
$zip.Dispose()
return "$Directory\$folder\$filename"
if($removeitem -eq "true"){ remove-item "$Directory\$filename" }
}
function Upload-Win32Lob()
{
<#
.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
Upload-Win32Lob "C:\Packages\package.intunewin" -publisher "Microsoft" -description "Package"
This example uses all parameters required to add an intunewin File into the Intune Service
.EXAMPLE
Upload-Win32Lob -SourceFile "$SourceFile" -displayName "Application Name" -publisher "Publisher" -description "Description" -detectionRules $DetectionRule -returnCodes $ReturnCodes
-installCmdLine "powershell.exe -executionpolicy Bypass .\install.ps1" -uninstallCmdLine "powershell.exe -executionpolicy Bypass .\uninstall.ps1"
This example uses additional parameters
#>
[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-Host "Testing if SourceFile '$SourceFile' Path is valid..." -ForegroundColor Yellow
Test-SourceFile "$SourceFile"
$Win32Path = "$SourceFile"
Write-Host "Creating JSON data to pass to the service..." -ForegroundColor Yellow
# 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 = GetWin32AppBody `
-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 = GetWin32AppBody -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-Host "Creating application in Intune..." -ForegroundColor Yellow
$mobileApp = MakePostRequest "mobileApps" ($mobileAppBody | ConvertTo-Json);
# Get the content version for the new app (this will always be 1 until the new app is committed).
Write-Host "Creating Content Version in the service for the application..." -ForegroundColor Yellow
$appId = $mobileApp.id;
$contentVersionUri = "mobileApps/$appId/$LOBType/contentVersions";
$contentVersion = MakePostRequest $contentVersionUri "{}";
# Encrypt file and Get File Information
Write-Host "Getting Encryption Information for '$SourceFile'..." -ForegroundColor Yellow
$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-Host "Creating a new file entry in Azure for the upload..." -ForegroundColor Yellow
$contentVersionId = $contentVersion.id;
$fileBody = GetAppFileBody "$FileName" $Size $EncrySize $null;
$filesUri = "mobileApps/$appId/$LOBType/contentVersions/$contentVersionId/files";
$file = MakePostRequest $filesUri ($fileBody | ConvertTo-Json);
# Wait for the service to process the new file request.
Write-Host "Waiting for the file entry URI to be created..." -ForegroundColor Yellow
$fileId = $file.id;
$fileUri = "mobileApps/$appId/$LOBType/contentVersions/$contentVersionId/files/$fileId";
$file = WaitForFileProcessing $fileUri "AzureStorageUriRequest";
# Upload the content to Azure Storage.
Write-Host "Uploading file to Azure Storage..." -f Yellow
$sasUri = $file.azureStorageUri;
UploadFileToAzureStorage $file.azureStorageUri "$IntuneWinFile" $fileUri;
# Need to Add removal of IntuneWin file
$IntuneWinFolder = [System.IO.Path]::GetDirectoryName("$IntuneWinFile")
Remove-Item "$IntuneWinFile" -Force
# Commit the file.
Write-Host "Committing the file into Azure Storage..." -ForegroundColor Yellow
$commitFileUri = "mobileApps/$appId/$LOBType/contentVersions/$contentVersionId/files/$fileId/commit";
MakePostRequest $commitFileUri ($fileEncryptionInfo | ConvertTo-Json);
# Wait for the service to process the commit file request.
Write-Host "Waiting for the service to process the commit file request..." -ForegroundColor Yellow
$file = WaitForFileProcessing $fileUri "CommitFile";
# Commit the app.
Write-Host "Committing the file into Azure Storage..." -ForegroundColor Yellow
$commitAppUri = "mobileApps/$appId";
$commitAppBody = GetAppCommitBody $contentVersionId $LOBType;
MakePatchRequest $commitAppUri ($commitAppBody | ConvertTo-Json);
Write-Host "Sleeping for $sleep seconds to allow patch completion..." -f Magenta
Start-Sleep $sleep
Write-Host
}
catch {
Write-Host "";
Write-Host -ForegroundColor Red "Aborting with exception: $($_.Exception.ToString())";
}
}
$ClientId = "" #Enter your Client Id for getting MS Graph access token with permission: DeviceManagementApps.ReadWrite.All
$global:headers = Get-MsiAuthHeaders -resourceUri "https://graph.microsoft.com" -clientId $ClientId
$baseUrl = "https://graph.microsoft.com/beta/deviceAppManagement/"
$logRequestUris = $true;
$logHeaders = $false;
$logContent = $true;
$azureStorageUploadChunkSizeInMb = 6l;
$sleep = 30
$SourceFile = "" #Full path to .intunewin file
$PowerShellDetectionScript = "" #Full path to Powershell Detection script
$PowerShellDetectionRule = New-DetectionRule -PowerShell -ScriptFile "$PowerShellDetectionScript" -enforceSignatureCheck $false -runAs32Bit $true
$Publisher = "" #Publisher of app
$Description = "" #Description of app
# Creating Array for detection Rule
$DetectionRule = @($PowerShellDetectionRule)
$ReturnCodes = Get-DefaultReturnCodes
# Win32 Application Upload
Upload-Win32Lob -SourceFile "$SourceFile" -publisher $Publisher -description $Description -detectionRules $DetectionRule -returnCodes $ReturnCodes

Двоичные данные
ProvisionWin32AppAutomation/docs/CreateIntunewinFile.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 109 KiB

Двоичные данные
ProvisionWin32AppAutomation/docs/ProvisionWin32App.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Просмотреть файл

@ -0,0 +1,29 @@
# Provision Win32 App
Powershell script used to Provision a Win 32 App to be uploaded to Intune. It works by getting a Graph API token using an MSI Client Id and then uploads the intunewin file into Intune.
## Prerequisites
- Windows 10 version 1607 or later (Enterprise, Pro, and Education versions)
- Client should have Graph API permission: **DeviceManagementApps.ReadWrite.All**
- Powershell ISE
- Microsoft Win32 Content Prep Tool
- Application file being uploaded to Intune
### Steps:
1. Download Microsoft Win32 Content Prep Tool
2. Run the IntuneWinAppUtil.exe and specify the following parameters: folder with setup files, path to .exe or .msi application file, and the output folder to send the .intunewin file
3. Open PowerShell ISE in elevated mode and open the following script: Provision-Win32App.ps1
4. Manually configure the following variables for your own environment:
- ClientId: Azure MSI Client Id used to get the Graph API access token
- SourceFile: This is the path to the Intunewin file
- PowerShellDetectionScript: This is the path to the detection script
- Publisher: The publisher of the application
- Description: Description of the application
5. Modify New-DetectionRule, Get-DefaultReturnCodes, and Upload-Win32Lob function parameters as needed based on requirmements
6. Run the Powershell script
## Notes
The Provision Win32 App script was originally created for use inside of Microsoft. We have modified it to be more generic, so it can be used as a template for other Intune environments outside of Microsoft.

Просмотреть файл

@ -0,0 +1,160 @@
<#
.SYNOPSIS
Verify Delete Intune Mobile Apps, Device Configuration Profiles, and Group Policy Configurations
.DESCRIPTION
Powershell script used to verify objects exist in Intune before deletion.
It works by getting a Graph API token using an MSI Client Id
and verifies Intune objects exist using the Graph API.
.PARAMETER MobileAppClientId
Azure MSI Client Id used to get the Graph API access token for Intune Mobile Apps
.PARAMETER DeviceConfigProfileClientId
Azure MSI Client Id used to get the Graph API access token for Intune Device Configuration Profiles
.PARAMETER GroupPolicyClientId
Azure MSI Client Id used to get the Graph API access token for Intune Group Policy Configurations
.PARAMETER MobileAppId
The Intune Mobile Application Id being verified
.PARAMETER DeviceConfigurationProfileId
The Intune Device Configuration Profile Id being verified
.PARAMETER GroupPolicyConfigurationId
The Intune Group Policy Configuration Id being verified
.NOTES
Version: 1.0
Creation date: 2/6/2023
Purpose/Change: Open Source example
#>
function Get-MsiAuthHeaders
{
param
(
[Parameter(Mandatory=$true)]
[Validateset("https://graph.microsoft.com","https://vault.azure.net","https://management.azure.com","https://database.windows.net")]
$ResourceUri,
[Parameter(Mandatory=$true)]
$ClientId
)
$msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=$($ResourceUri)&client_id=$($ClientId)"
$response = Invoke-WebRequest -Uri $msiEndpoint -Headers @{"Metadata"= $true} -UseBasicParsing
$responseJson = ConvertFrom-Json -InputObject $response
$AccessToken = $responseJson.access_token
$Headers = @{
'Content-Type'='application/json'
'Authorization'="Bearer " + $AccessToken
}
return $Headers
}
function Verify-IntuneMobileApp {
param(
$MobileAppId
)
try
{
$MobileAppClientId = "" #Enter your Client Id for getting access token
#This Client Id must have Graph API permission: DeviceManagementApps.Read.All
Write-Output "Getting microsoft graph api access headers using client id - $($MobileAppClientId)"
$AppHeaders = Get-MsiAuthHeaders -ResourceUri "https://graph.microsoft.com" -ClientId $MobileAppClientId
$AppGraphUrl = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$($mobileAppId)"
Write-Output "Getting Mobile app using Url: $($AppGraphUrl)"
$MobileApp = Invoke-RestMethod -Uri $AppGraphUrl -Method Get -Headers $AppHeaders
if($MobileApp)
{
$MobileAppName = $MobileApp.displayName
Write-Output "Successfully found Intune Mobile App - $($MobileAppName) with MobileAppId - $($MobileAppId) in Intune"
}
else
{
Write-Output "Intune Mobile Application with mobileAppId: $($mobileAppId) not found"
}
}
catch [System.Exception]
{
Write-Output "Intune Mobile App Verify Deletion Failed with id: $($MobileAppId)"
Write-Error $_.Exception.Message
}
}
function Verify-IntuneDeviceConfigurationProfile {
param(
$DeviceConfigProfileId
)
try
{
$DeviceConfigProfileClientId = "" #Enter your Client Id for getting access token
#This Client Id must have Graph API permission: DeviceManagementConfiguration.Read.All
Write-Output "Getting microsoft graph api access headers using client id - $($DeviceConfigProfileClientId)"
$DeviceConfigProfileHeaders = Get-MsiAuthHeaders -ResourceUri "https://graph.microsoft.com" -ClientId $DeviceConfigProfileClientId
$DeviceConfigProfileGraphUrl = "https://graph.microsoft.com/v1.0/deviceManagement/deviceConfigurations/$($DeviceConfigProfileId)"
Write-Output "Getting Device Configuration Profile using Url: $($DeviceConfigProfileGraphUrl)"
$DeviceConfigProfile = Invoke-RestMethod -Uri $DeviceConfigProfileGraphUrl -Method Get -Headers $DeviceConfigProfileHeaders
if($DeviceConfigProfile)
{
$DeviceConfigProfileName = $DeviceConfigProfile.displayName
Write-Output "Successfully found Intune Device Configuration Profile - $($DeviceConfigProfileName) with DeviceConfigProfileId - $($DeviceConfigProfileId) in Intune"
}
else
{
Write-Output "Intune Device Configuration Profile with DeviceConfigProfileId: $($DeviceConfigProfileId) not found"
}
}
catch [System.Exception]
{
Write-Output "Intune Device Configuration Profile Verify Deletion Failed with id: $($DeviceConfigProfileId)"
Write-Error $_.Exception.Message
}
}
function Verify-IntuneGroupPolicy {
param(
$GroupPolicyConfigurationId
)
try
{
$GroupPolicyClientId = "" #Enter your Client Id for getting access token
#This Client Id must have Graph API permission: DeviceManagementConfiguration.Read.All
Write-Output "Getting microsoft graph api access headers using client id - $($GroupPolicyClientId)"
$GroupPolicyHeaders = Get-MsiAuthHeaders -ResourceUri "https://graph.microsoft.com" -ClientId $GroupPolicyClientId
$GroupPolicyConfigurationUrl = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations/$($GroupPolicyConfigurationId)"
Write-Output "Getting Group Policy Configuration using Url: $($GroupPolicyConfigurationUrl)"
$GroupPolicy = Invoke-RestMethod -Uri $GroupPolicyConfigurationUrl -Method Get -Headers $GroupPolicyHeaders
if($GroupPolicy)
{
$GroupPolicyName = $GroupPolicy.displayName
Write-Output "Successfully fouund Group Policy Configuration - $($GroupPolicyName) with GroupPolicyConfigurationId - $($GroupPolicyConfigurationId) in Intune"
}
else
{
Write-Output "Intune Group Policy Configuration with GroupPolicyConfigurationId: $($GroupPolicyConfigurationId) not found"
}
}
catch [System.Exception]
{
Write-Output "Intune Group Policy Configuration Verify Deletion Failed with id: $($GroupPolicyConfigurationId)"
Write-Error $_.Exception.Message
}
}
Write-Output "------------------- Starting Verify Intune Object Deletion Script -------------------"
$MobileAppId = '' #Mobile App Id to verify before deletion
$DeviceConfigurationProfileId = '' #Device Configuration Profile Id to verify before deletion
$GroupPolicyConfigurationId = '' #Group Policy Configuration Id to verify before deletion
if($MobileAppId)
{
Verify-IntuneMobileApp -MobileAppId $MobileAppId
}
if($DeviceConfigurationProfileId)
{
Verify-IntuneDeviceConfigurationProfile -DeviceConfigProfileId $DeviceConfigurationProfileId
}
if($GroupPolicyConfigurationId)
{
Verify-IntuneGroupPolicy -GroupPolicyConfigurationId $DeviceGroupPolicyId
}

Двоичные данные
VerifyDeletionAutomation/docs/VerifyDeletionAutomation.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Просмотреть файл

@ -0,0 +1,25 @@
# Verify Delete Intune Automation
Powershell script used to verify objects are in Intune. It works by getting a Graph API token using an MSI Client Id and gets the Intune object using the Graph API.
## Prerequisites
- To verify Intune Mobile Apps, MSI should have Graph API permission: **DeviceManagementApps.Read.All**
- To verify Intune Device Configuration Profiles or Group Policy Configurations, MSI should have Graph API permission: **DeviceManagementConfiguration.Read.All**
- Powershell ISE
### Steps:
1. Open PowerShell ISE in elevated mode and open the following script: Verify-DeleteIntuneObjects.ps1
2. Manually configure the following variables for your own environment:
- MobileAppClientId: Azure MSI Client Id used to get the Graph API access token
- DeviceConfigProfileClientId: Azure MSI Client Id used to get the Graph API access token
- GroupPolicyClientId: Azure MSI Client Id used to get the Graph API access token
- MobileAppId: The Intune Mobile Application Id being verified
- DeviceConfigurationProfileId: The Intune Device Configuration Profile Id being verified
- GroupPolicyConfigurationId: The Intune Group Policy Configuration Id being verified
3. Run the Powershell script
## Notes
The Verify Delete Intune Automation script was originally created for use inside of Microsoft. We have modified it to be more generic, so it can be used as a template for other Intune environments outside of Microsoft.