diff --git a/Extensions/AppProtection.psm1 b/Extensions/AppProtection.psm1 index 4a3e463..7fedeff 100644 --- a/Extensions/AppProtection.psm1 +++ b/Extensions/AppProtection.psm1 @@ -198,6 +198,16 @@ function Get-AppProtectionObjectType { "androidManagedAppProtections" } + elseif($odataType -like "*mdmWindowsInformationProtectionPolicy*") + { + # Win 10 - With enrollment e.g. Intune enrolled Win 10 devices + "mdmWindowsInformationProtectionPolicies" + } + elseif($odataType -like "*windowsInformationProtectionPolicy*") + { + # Win 10 - Without enrollment e.g. MAM polices for Win 10 + "WindowsInformationProtectionPolicies" + } } function Get-AppProtectionObjectForExport @@ -257,10 +267,8 @@ function Import-AppProtection if(($obj | GM -MemberType NoteProperty -Name "Apps")) { $apps = $obj.Apps - # Remove apps properties - Remove-ObjectProperty $obj "apps" - Remove-ObjectProperty $obj "apps@odata.context" } + Start-PreImport $obj -RemoveProperties @("apps","apps@odata.context") Write-Status "Import $($obj.displayName)" diff --git a/Extensions/Apps.psm1 b/Extensions/Apps.psm1 index 2619986..52b15b1 100644 --- a/Extensions/Apps.psm1 +++ b/Extensions/Apps.psm1 @@ -247,17 +247,7 @@ function Import-Application { param($obj) - Remove-ObjectProperty $obj "uploadState" - Remove-ObjectProperty $obj "publishingState" - Remove-ObjectProperty $obj "isAssigned" - Remove-ObjectProperty $obj "roleScopeTagIds" - Remove-ObjectProperty $obj "dependentAppCount" - Remove-ObjectProperty $obj "committedContentVersion" - Remove-ObjectProperty $obj "id" - Remove-ObjectProperty $obj "createdDateTime" - Remove-ObjectProperty $obj "lastModifiedDateTime" - Remove-ObjectProperty $obj "isFeatured" - Remove-ObjectProperty $obj "size" + Start-PreImport $obj -RemoveProperties @("uploadState","publishingState","isAssigned","roleScopeTagIds","dependentAppCount","committedContentVersion","id","isFeatured","size") Write-Status "Import $($obj.displayName)" diff --git a/Extensions/AutoPilot.psm1 b/Extensions/AutoPilot.psm1 index 3db94d1..20c4409 100644 --- a/Extensions/AutoPilot.psm1 +++ b/Extensions/AutoPilot.psm1 @@ -203,6 +203,8 @@ function Import-AutoPilot Write-Status "Import $($obj.displayName)" + Start-PreImport $obj + Invoke-GraphRequest -Url "/deviceManagement/windowsAutopilotDeploymentProfiles" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST } diff --git a/Extensions/AzureNative.psm1 b/Extensions/AzureNative.psm1 index 553fd72..9f61227 100644 --- a/Extensions/AzureNative.psm1 +++ b/Extensions/AzureNative.psm1 @@ -1,6 +1,82 @@ +#Requires -module Az.Accounts + +function Invoke-InitializeModule +{ + if(-not $global:AzToken) + { + # Only allow re-logging if it failed the first time + $global:AuthenticatedToAzure = $false + + } + #!!! - Used for testing login + #Disconnect-AzAccount -Username admin@delematelab2.onmicrosoft.com +} + +function Connect-AzureNative +{ + <# + .SYNOPSIS + Tries to connect to Azure with existing token + Uses Connect-AZAccount if no token found in cache + #> + + param($user) + + Write-Log "Authenticate to Azure (Az module). Try from cache with user $user" + + $Context = (Get-AzContext -ListAvailable | Where { $_.Account.Id -eq $user } | select -first 1) + + if (-not $Context) + { + $user | Clip # Copy login id to clipboard + + # Run Connect-AZAccount in a separate runspace or it will hang + $Runspace = [runspacefactory]::CreateRunspace() + $PowerShell = [powershell]::Create() + $PowerShell.Runspace = $Runspace + $Runspace.Open() + $PowerShell.AddScript({Connect-AZAccount}) + $PowerShell.Invoke() + + [System.Windows.Forms.Application]::DoEvents() + + $Context = (Get-AzContext -ListAvailable | Where { $_.Account.Id -eq $user } | select -first 1) + } + $global:AzToken = "" + try + { + $Resource = '74658136-14ec-4630-ad9b-26e160ff0fc6' + $global:AzToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id, $null, "Never", $null, $Resource) + } + catch + { + Write-LogError "Failed to authenticate with Instance.AuthenticationFactory.Authenticate" $_.Exception + } + + if(-not $global:AzToken) + { + Write-Log "Failed to authenticate" 3 + } + else + { + Write-Log "Authenticated as $($global:AzToken.UserId)" + } + $global:AuthenticatedToAzure = $true + + Set-MainTitle +} + +# Invoke-AzureNativeRequest is based on the following project +# https://github.com/JustinGrote/Az.PortalAPI/tree/master/Az.PortalAPI # -# Azure functions are based on: ??? -# +# Some small changes: +# - Get-AzContext is based on the same user as Intune user +# - Renamed Invoke-Request to Invoke-AzureNativeRequest +# - Added support for HTTP Method PATCH +# - Added support for paging with nextLink (Lazy solution...not fully tested but looks like it is working) +# - Removed Token parameter. Created the Connect-AzureNative to get token +# - Removed Context parameter + function Invoke-AzureNativeRequest { <# .SYNOPSIS @@ -19,7 +95,7 @@ function Invoke-AzureNativeRequest { $Body, #Specify the HTTP Method you wish to use. Defaults to GET - [ValidateSet("GET","POST","OPTIONS","DELETE", "PATCH", "PUT")] + [ValidateSet("GET","POST","OPTIONS","DELETE","PATCH")] $Method = "GET", #The base URI for the Portal API. Typically you don't need to change this @@ -29,101 +105,76 @@ function Invoke-AzureNativeRequest { #The request ID for the session. You can generate one with [guid]::NewGuid().guid. #Typically you only specify this if you're trying to retry an operation and don't want to duplicate the request, such as for a POST operation - $requestID = [guid]::NewGuid().guid + $requestID = [guid]::NewGuid().guid, + + [switch]$allowPaging ) + if(-not $global:AzToken -and $global:AuthenticatedToAzure -eq $false) + { + Connect-AzureNative $global:me.userPrincipalName + } + + if(-not $global:AzToken) + { + return + } + #Combine the BaseURI and Target [String]$ApiAction = $Target.TrimStart('/') - if ($Action) - { + if ($Action) { $ApiAction = $ApiAction + '/' + $Action } - if($global:tokresponse -and [DateTimeOffset]::Now.ToUnixTimeSeconds() -gt $global:tokresponse.expires_on) + $uriStr = "$baseURI$ApiAction" + + if($allowPaging) { - $global:tokresponse = $null + $uri = [Uri]::New("$uriStr&nextLink=null") + } + else + { + $uri = [Uri]::New($baseURI,$ApiAction) } - $Context = Get-AzureRmContext - - if(-not $context -or -not $global:tokresponse) + if(-not $global:AzToken.AccessToken.tostring()) { - if($Context) - { - if($global:Me -and $global:Organization) - { - $refreshToken = ($Context.TokenCache.ReadItems() | Where { $_.DisplayableId -eq $global:Me.userPrincipalName -and $_.TenantId -eq $global:Organization.Id }).RefreshToken - if($refreshToken -and $refreshToken.ExpiresOn -lt (Get-Date)) - { - # Expired...force login - # $refreshToken = $null - } - } - } - - if(-not $refreshToken) - { - $user = Connect-AzureRmAccount - if(-not $user) { return } - $Context = Get-AzureRmContext - if(-not $Context) { return } - } - } - - # - if(-not $global:tokresponse) - { - $refreshToken = $null # Fore read again in case of login - if($global:Me -and $global:Organization) - { - $refreshToken = ($Context.TokenCache.ReadItems() | Where { $_.DisplayableId -eq $global:Me.userPrincipalName -and $_.TenantId -eq $global:Organization.Id }).RefreshToken - } - # Make sure we are using the same user as Intune login - if(-not $refreshToken) - { - [System.Windows.MessageBox]::Show("Failed to find login token for AzureRM", "Invalid AzureRM login!", "OK", "Error") - return $global:tokresponse - } - - $curToken = $Context.TokenCache.ReadItems() | Where { $_.DisplayableId -eq $global:Me.userPrincipalName -and $_.TenantId -eq $global:Organization.Id } - $tenantid = $curToken.TenantId - $refreshToken = $curToken.RefreshToken - $loginUrl = "https://login.windows.net/$tenantid/oauth2/token" - $bodyTmp = "grant_type=refresh_token&refresh_token=$($refreshToken)" #&resource=74658136-14ec-4630-ad9b-26e160ff0fc6" - $response = Invoke-RestMethod $loginUrl -Method POST -Body $bodyTmp -ContentType 'application/x-www-form-urlencoded' - - $global:tokresponse = Invoke-RestMethod $loginUrl -Method POST -Body ($bodyTmp + "&resource=74658136-14ec-4630-ad9b-26e160ff0fc6") - - if(-not $global:tokresponse) { return } + Write-Log "No access token available" 3 + return } $InvokeRestMethodParams = @{ - Uri = [Uri]::New($baseURI,$ApiAction) + Uri = $uri Method = $Method Header = [ordered]@{ - Authorization = 'Bearer ' + $global:tokresponse.access_token + Authorization = 'Bearer ' + $global:AzToken.AccessToken.tostring() 'Content-Type' = 'application/json' 'x-ms-client-request-id' = $requestID 'Host' = $baseURI.Host - 'Origin' = $requestOrigin + 'Origin' = 'https://iam.hosting.portal.azure.net' } Body = $Body } - try + $max = 100 + $cur = 0 + + $retObj = Invoke-RestMethod @InvokeRestMethodParams + if(($retObj | GM -MemberType NoteProperty -Name "nextLink")) { - Invoke-RestMethod @InvokeRestMethodParams - if($? -eq $false) + while($retObj.nextLink) { - throw $global:error[0] - } + # Get more objects + $InvokeRestMethodParams["Uri"] = [Uri]::New($uriStr + "&nextLink=" + $retObj.nextLink) + $retObj = Invoke-RestMethod @InvokeRestMethodParams + if($cur -ge $max) { break } + $cur++ # Loop gets stuck if nextLink=null is added to the command line so make sure it doesn't hang forever + } } - catch - { - Write-LogError "Failed to invoke Invoke-RestMethod for Azure" $_.Exception - } + + $retObj } function Get-AzureNativeObjects @@ -135,10 +186,11 @@ function Get-AzureNativeObjects $property, [Array] $exclude, - $SortProperty = "") + $SortProperty = "", + [switch]$allowPaging) $objects = @() - $nativeObjects =Invoke-AzureNativeRequest $Target + $nativeObjects = Invoke-AzureNativeRequest $Target -allowPaging:($allowPaging -eq $true) if(($nativeObjects | GM -Name "items")) { diff --git a/Extensions/Branding.psm1 b/Extensions/Branding.psm1 index ec20ce4..70120fa 100644 --- a/Extensions/Branding.psm1 +++ b/Extensions/Branding.psm1 @@ -188,7 +188,7 @@ function Import-IntuneBranding { param($obj) - Remove-ObjectProperty $obj "@odata.context" + Start-PreImport $obj -RemoveProperties @("@odata.context") $newObject = @" { diff --git a/Extensions/CompliancePolicies.psm1 b/Extensions/CompliancePolicies.psm1 index 29a66bc..fa2a53b 100644 --- a/Extensions/CompliancePolicies.psm1 +++ b/Extensions/CompliancePolicies.psm1 @@ -201,6 +201,8 @@ function Import-CompliancePolicy { param($obj) + Start-PreImport $obj + $json = ConvertTo-Json $obj -Depth 5 $json = $json.Trim().TrimEnd('}').Trim() $json += @" diff --git a/Extensions/ConditionalAccess.psm1 b/Extensions/ConditionalAccess.psm1 index d4aeec2..e285d1c 100644 --- a/Extensions/ConditionalAccess.psm1 +++ b/Extensions/ConditionalAccess.psm1 @@ -105,7 +105,7 @@ function Get-ConditionalAccess function Get-ConditionalAccessObjects { #https://main.iam.ad.ext.azure.com/api/Policies/Policies?top=10&nextLink=null&appId=&includeBaseline=true - Get-AzureNativeObjects "Policies/Policies?top=10&nextLink=null&appId=&includeBaseline=true" -property @('policyName') + Get-AzureNativeObjects "Policies/Policies?top=10&appId=&includeBaseline=true" -property @('policyName') -allowPaging } function Get-ConditionalAccessObject @@ -214,6 +214,8 @@ function Import-ConditionalAccess { param($obj) + Start-PreImport $obj + $json = Update-JsonForEnvironment $json if($obj.baselineType -eq 0) diff --git a/Extensions/ConfigurationItems.psm1 b/Extensions/ConfigurationItems.psm1 index cff05cd..7443aa0 100644 --- a/Extensions/ConfigurationItems.psm1 +++ b/Extensions/ConfigurationItems.psm1 @@ -206,14 +206,10 @@ function Import-DeviceConfiguration { param($obj) - if(($obj | GM -MemberType NoteProperty -Name "supportsScopeTags")) - { - # Remove read-only property - $obj.PSObject.Properties.Remove('supportsScopeTags') - } - Write-Status "Import $($obj.displayName)" + Start-PreImport $obj + Invoke-GraphRequest -Url "/deviceManagement/deviceConfigurations" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST } diff --git a/Extensions/EnrollmentStatusPage.psm1 b/Extensions/EnrollmentStatusPage.psm1 index 8b0f730..0c85d51 100644 --- a/Extensions/EnrollmentStatusPage.psm1 +++ b/Extensions/EnrollmentStatusPage.psm1 @@ -222,6 +222,8 @@ function Import-ESP { param($obj) + Start-PreImport $obj + if($obj.id -like "*_default*") { Write-Status "Update $($obj.displayName)" diff --git a/Extensions/GroupPolicy.psm1 b/Extensions/GroupPolicy.psm1 index 543c0e4..5211b1a 100644 --- a/Extensions/GroupPolicy.psm1 +++ b/Extensions/GroupPolicy.psm1 @@ -261,6 +261,8 @@ function Import-GPOSetting Write-Status "Import $($obj.displayName)" + Start-PreImport $obj + # Import Administrative Template profile $response = Invoke-GraphRequest -Url "/deviceManagement/groupPolicyConfigurations" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST @@ -268,6 +270,8 @@ function Import-GPOSetting { foreach($setting in $settings) { + Start-PreImport $setting + # Import each setting for the Administrative Template profile $response2 = Invoke-GraphRequest -Url "/deviceManagement/groupPolicyConfigurations/$($response.id)/definitionValues" -Content (ConvertTo-Json $setting -Depth 5) -HttpMethod POST } diff --git a/Extensions/MSGraphIntune.psm1 b/Extensions/MSGraphIntune.psm1 index fa0b866..51095af 100644 --- a/Extensions/MSGraphIntune.psm1 +++ b/Extensions/MSGraphIntune.psm1 @@ -3,33 +3,34 @@ function Invoke-InitializeModule $module = Get-Module -Name Microsoft.Graph.Intune -ListAvailable if(-not $module) { - $ret = [System.Windows.MessageBox]::Show("Intune PowerShell module not found!`n`nDo you want to install it?`n`nYes = Install intune module (Requires admin or it will fail)`nNo = Contune without module (No Azure modules will be loaded)`nCancel = Quit", "Error", "YesNoCancel", "Error") - if($ret -eq "Yes") - { - try - { - Install-Module -Name Microsoft.Graph.Intune -Force -ErrorAction SilentlyContinue - } - catch {} - if(-not (Get-Module -Name Microsoft.Graph.Intune -ListAvailable -Refresh)) - { - [System.Windows.MessageBox]::Show("Failed to install Intune PowerShell module!`n`nRestart this as admin and try again`nor`nStart PowerShell as admin and run:`nInstall-Module -Name Microsoft.Graph.Intune", "Error", "OK", "Error") - exit - } - } - elseif($ret -eq "Cancel") + $ret = [System.Windows.MessageBox]::Show("Intune PowerShell module not found!`n`nDo you want to install it as admin?`n`nYes = Install intune module as Admin (Requires admin or it will fail)`nNo = Install module for current user`nCancel = Quit", "Error", "YesNoCancel", "Error") + if($ret -eq "Cancel") { exit } - else + + $params = @{} + if($ret -eq "No") { - return + $params.Add("Scope", "CurrentUser") + } + + try + { + Install-Module -Name Microsoft.Graph.Intune -Force -ErrorAction SilentlyContinue @params + } + catch {} + + if(-not (Get-Module -Name Microsoft.Graph.Intune -ListAvailable -Refresh)) + { + [System.Windows.MessageBox]::Show("Failed to install Intune PowerShell module!`n`nRestart this as admin and try again`nor`nStart PowerShell as admin and run:`nInstall-Module -Name Microsoft.Graph.Intune", "Error", "OK", "Error") + exit } } if(-not $global:authentication) { - if((Get-Command Connect-MSGraph)) + if((Get-Command Connect-MSGraph -ErrorAction SilentlyContinue)) { $global:authentication = Connect-MSGraph -PassThru } @@ -41,6 +42,7 @@ function Invoke-InitializeModule return } + Write-Log "Get current user" $global:Me = Invoke-GraphRequest "ME" if(-not $global:Me) @@ -48,6 +50,8 @@ function Invoke-InitializeModule [System.Windows.MessageBox]::Show("Failed to get information about current logged on Azure user!`n`nVerify connection and try again`n`nNo Intune modules will be imported!", "Error", "OK", "Error") return } + + Write-Log "Get organization info" $global:Organization = (Invoke-GraphRequest "Organization").Value $global:graphURL = "https://graph.microsoft.com/beta" @@ -59,6 +63,8 @@ function Invoke-InitializeModule Values = @() }) + Write-Log "Add settings and menu items" + Add-SettingsObject (New-Object PSObject -Property @{ Title = "Root folder" Key = "IntuneRootFolder" @@ -76,6 +82,7 @@ function Invoke-InitializeModule Key = "AddObjectType" Type = "Boolean" DefaultValue = $true + Description = "Default setting for adding object type to the export folder" }) "IntuneAzure" Add-SettingsObject (New-Object PSObject -Property @{ @@ -83,6 +90,7 @@ function Invoke-InitializeModule Key = "AddCompanyName" Type = "Boolean" DefaultValue = $true + Description = "Default setting for adding company name to the export folder" }) "IntuneAzure" Add-SettingsObject (New-Object PSObject -Property @{ @@ -90,6 +98,7 @@ function Invoke-InitializeModule Key = "ExportIntuneAssignments" Type = "Boolean" DefaultValue = $true + Description = "Default setting for exporting assignments" }) "IntuneAzure" Add-SettingsObject (New-Object PSObject -Property @{ @@ -97,6 +106,7 @@ function Invoke-InitializeModule Key = "CreateIntuneGroupOnImport" Type = "Boolean" DefaultValue = $true + Description = "Default setting for creating groups during import" }) "IntuneAzure" Add-SettingsObject (New-Object PSObject -Property @{ @@ -104,6 +114,7 @@ function Invoke-InitializeModule Key = "ConvertIntuneSyncedGroupOnImport" Type = "Boolean" DefaultValue = $true + Description = "Convert AD synched groups to Azure AD group during import if the group does not exist" }) "IntuneAzure" Add-SettingsObject (New-Object PSObject -Property @{ @@ -178,7 +189,6 @@ $xmlStr = @" -