From b6eb4875ecf8c6cf74ebfbb857adb20ccfa33a54 Mon Sep 17 00:00:00 2001
From: Mikael Karlsson <43226266+Micke-K@users.noreply.github.com>
Date: Mon, 10 Jun 2019 19:13:33 +1000
Subject: [PATCH] Add files via upload
---
Extensions/AppProtection.psm1 | 304 ++++++
Extensions/Apps.psm1 | 924 +++++++++++++++++
Extensions/AutoPilot.psm1 | 238 +++++
Extensions/AzureBranding.psm1 | 310 ++++++
Extensions/AzureNative.psm1 | 169 +++
Extensions/Baseline.psm1 | 294 ++++++
Extensions/Branding.psm1 | 236 +++++
Extensions/CompliancePolicies.psm1 | 244 +++++
Extensions/ConditionalAccess.psm1 | 273 +++++
Extensions/ConfigurationItems.psm1 | 246 +++++
Extensions/EnrollmentStatusPage.psm1 | 277 +++++
Extensions/GroupPolicy.psm1 | 322 ++++++
Extensions/MDM_MAM.psm1 | 215 ++++
Extensions/MSGraphIntune.psm1 | 1438 ++++++++++++++++++++++++++
Extensions/PowerShellScripts.psm1 | 306 ++++++
Extensions/TermsAndConditions.psm1 | 243 +++++
16 files changed, 6039 insertions(+)
create mode 100644 Extensions/AppProtection.psm1
create mode 100644 Extensions/Apps.psm1
create mode 100644 Extensions/AutoPilot.psm1
create mode 100644 Extensions/AzureBranding.psm1
create mode 100644 Extensions/AzureNative.psm1
create mode 100644 Extensions/Baseline.psm1
create mode 100644 Extensions/Branding.psm1
create mode 100644 Extensions/CompliancePolicies.psm1
create mode 100644 Extensions/ConditionalAccess.psm1
create mode 100644 Extensions/ConfigurationItems.psm1
create mode 100644 Extensions/EnrollmentStatusPage.psm1
create mode 100644 Extensions/GroupPolicy.psm1
create mode 100644 Extensions/MDM_MAM.psm1
create mode 100644 Extensions/MSGraphIntune.psm1
create mode 100644 Extensions/PowerShellScripts.psm1
create mode 100644 Extensions/TermsAndConditions.psm1
diff --git a/Extensions/AppProtection.psm1 b/Extensions/AppProtection.psm1
new file mode 100644
index 0000000..10bdcf7
--- /dev/null
+++ b/Extensions/AppProtection.psm1
@@ -0,0 +1,304 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-AppProtectionName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-AppProtections}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-AppProtectionName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all app protection/configuration policies"
+ Import-AllAppProtectionObjects (Join-Path $rootFolder (Get-AppProtectionFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-AppProtectionName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all app protection/configuration policies"
+ Get-AppProtectionObjects | ForEach-Object { Export-SingleAppProtection $PSItem.Object (Join-Path $rootFolder (Get-AppProtectionFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-AppProtectionFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-AppProtectionName
+{
+ return "App Protection/Configuration"
+}
+
+function Get-AppProtectionFolderName
+{
+ return "AppProtection"
+}
+
+function Get-AppProtections
+{
+ Write-Status "Loading app protections and configurations"
+ $dgObjects.ItemsSource = @(Get-AppProtectionObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllAppProtections $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedAppProtection $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllAppProtectionObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-AppProtectionObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles}) -copy ([scriptblock]{Copy-AppProtection})
+}
+
+function Get-AppProtectionObjects
+{
+ Get-GraphObjects -Url "/deviceAppManagement/managedAppPolicies"
+}
+
+function Export-AllAppProtections
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleAppProtection $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedAppProtection
+{
+ param($path = "$env:Temp")
+
+ Export-SingleAppProtection $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleAppProtection
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-AppProtectionFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+
+ $obj = Get-AppProtectionObjectForExport $psObj
+
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+
+ Add-MigrationInfo $obj.assignments
+ }
+ $global:exportedObjects++
+ }
+}
+
+function Get-AppProtectionObjectType
+{
+ param($odataType)
+
+ if($odataType -like "*targetedManagedAppConfiguration*")
+ {
+ "targetedManagedAppConfigurations"
+
+ }
+ elseif($odataType -like "*iosManagedAppProtection*")
+ {
+ "iosManagedAppProtections"
+ }
+ elseif($odataType -like "*androidManagedAppProtection*")
+ {
+ "androidManagedAppProtections"
+ }
+}
+
+function Get-AppProtectionObjectForExport
+{
+ param($obj)
+
+ $objType = Get-AppProtectionObjectType $obj."@odata.type"
+
+ $expand = "?`$expand=assignments"
+ if($objType -eq "targetedManagedAppConfigurations")
+ {
+ $expand += ",Apps"
+ }
+
+ if($objType)
+ {
+ Invoke-GraphRequest -Url "/deviceAppManagement/$objType/$($obj.id)$($expand)"
+ }
+}
+
+function Copy-AppProtection
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect app protection/configuration item you want to copy", "Error", "OK", "Error")
+ return
+ }
+
+ $ret = Show-InputDialog "Copy app protection/configuration" "Select name for the new policy" "$($dgObjects.SelectedItem.displayName) - Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ $obj = Get-AppProtectionObjectForExport $dgObjects.SelectedItem.Object
+ if($obj)
+ {
+ # Remove assignment properties
+ Remove-ObjectProperty $obj "assignments"
+ Remove-ObjectProperty $obj "assignments@odata.context"
+
+ # Import new profile
+ $obj.displayName = $ret
+ Import-AppProtection $obj | Out-Null
+
+ $dgObjects.ItemsSource = @(Get-AppProtectionObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+function Import-AppProtection
+{
+ param($obj)
+
+ if(($obj | GM -MemberType NoteProperty -Name "Apps"))
+ {
+ $apps = $obj.Apps
+ # Remove apps properties
+ Remove-ObjectProperty $obj "apps"
+ Remove-ObjectProperty $obj "apps@odata.context"
+ }
+
+ Write-Status "Import $($obj.displayName)"
+
+ $objType = Get-AppProtectionObjectType $obj."@odata.context"
+
+ if($objType)
+ {
+ #Import the app configuration policy
+ $response = Invoke-GraphRequest -Url "/deviceAppManagement/$objType" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST
+ if($response -and $apps)
+ {
+ # Import targeted apps
+ $response2 = Invoke-GraphRequest -Url "/deviceAppManagement/$objType/$($response.Id)/targetApps" -Content "{ apps: $(ConvertTo-Json $apps -Depth 5)}" -HttpMethod POST
+ }
+ $response
+ }
+}
+
+function Import-AllAppProtectionObjects
+{
+ param($path = "$env:Temp")
+
+ Import-AppProtectionObjects (Get-JsonFileObjects $path)
+}
+
+function Import-AppProtectionObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import app protection/configuration policies"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import App Protection/Configuration: $($obj.Object.displayName)"
+
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+
+ $response = Import-AppProtection $obj.Object
+
+ if($response)
+ {
+ $global:importedObjects++
+
+ $dataType = Get-AppProtectionObjectType $response."@odata.context"
+
+ if($dataType)
+ {
+ Import-GraphAssignments $assignments "assignments" "/deviceAppManagement/$dataType/$($response.Id)/assign"
+ }
+ }
+ }
+ $dgObjects.ItemsSource = @(Get-AppProtectionObjects)
+ Write-Status ""
+}
\ No newline at end of file
diff --git a/Extensions/Apps.psm1 b/Extensions/Apps.psm1
new file mode 100644
index 0000000..cf8a701
--- /dev/null
+++ b/Extensions/Apps.psm1
@@ -0,0 +1,924 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-ApplicationName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-Applications}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-ApplicationName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all applications"
+ Import-AllApplicationObjects (Join-Path $rootFolder (Get-ApplicationFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-ApplicationName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all applications"
+ Get-ApplicationObjects | ForEach-Object { Export-SingleApplication $PSItem.Object (Join-Path $rootFolder (Get-ApplicationFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-ApplicationFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-ApplicationName
+{
+ (Get-ApplicationFolderName)
+}
+
+function Get-ApplicationFolderName
+{
+ "Applications"
+}
+
+function Get-Applications
+{
+ Write-Status "Loading applications"
+ $dgObjects.ItemsSource = @(Get-ApplicationObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllApplications $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedApplication $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllApplicationObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-ApplicationObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ $importExtension = (New-Object PSObject -Property @{
+ Xaml = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"@
+ Script = [ScriptBlock]{
+ param($form)
+ $script:txtPackagePath = $form.FindName("txtPackagePath")
+ $btnBrowsePackagePath = $form.FindName("btnBrowsePackagePath")
+ $script:txtPackagePath.Text = Get-SettingValue "IntuneAppPackages"
+
+ $btnBrowsePackagePath.Tag = $script:txtPackagePath
+ $btnBrowsePackagePath.Add_Click({
+ $folder = Get-Folder $this.Tag.Text
+ if($folder) { $this.Tag.Text = $folder }
+ })
+ }
+ })
+
+ $script:importParams = @{}
+ $script:importParams.Add("Extension", $importExtension)
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles @script:importParams}) # -copy ([scriptblock]{Copy-Application})
+}
+
+function Get-ApplicationObjects
+{
+ Get-GraphObjects -Url "/deviceAppManagement/mobileApps?`$filter=(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&`$orderby=displayName"
+}
+
+function Export-AllApplications
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleApplication $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedApplication
+{
+ param($path = "$env:Temp")
+
+ Export-SingleApplication $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleApplication
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-ApplicationFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+ $obj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$($psObj.id)?`$expand=assignments"
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+
+ Add-MigrationInfo $obj.assignments
+ }
+ $global:exportedObjects++
+ }
+}
+
+function Copy-Application
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect application item you want to copy", "Error", "OK", "Error")
+ return
+ }
+
+ $ret = Show-InputDialog "Copy application" "Select name for the new object" "$($dgObjects.SelectedItem.displayName) - Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ # Convert to Json and back to clone the object
+ $obj = ConvertTo-Json $dgObjects.SelectedItem.Object -Depth 5 | ConvertFrom-Json
+ if($obj)
+ {
+ # Import new profile
+ $obj.displayName = $ret
+ Import-Application $obj | Out-Null
+
+ $dgObjects.ItemsSource = @(Get-ApplicationObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+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"
+
+ Write-Status "Import $($obj.displayName)"
+
+ Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST
+}
+
+function Import-AllApplicationObjects
+{
+ param($path = "$env:Temp")
+
+ Import-ApplicationObjects (Get-JsonFileObjects $path)
+}
+
+function Import-ApplicationObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import applications"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ if($global:runningBulkImport)
+ {
+ $pkgPath = Get-SettingValue "IntuneAppPackages"
+ }
+ else
+ {
+ $pkgPath = $script:txtPackagePath.Text
+ }
+ $appFile = "$($pkgPath)\$($obj.Object.fileName)"
+
+ if(Test-Path $appFile)
+ {
+ Write-Log "Import Application: $($obj.Object.displayName) ($($obj.Object."@odata.type"))"
+
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+ $response = Import-Application $obj.Object
+
+ if($response)
+ {
+ $global:importedObjects++
+ Copy-AppPackageToIntune $appFile $response
+
+ Import-GraphAssignments $assignments "mobileAppAssignments" "/deviceAppManagement/mobileApps/$($response.Id)/assign" "#microsoft.graph.mobileAppAssignment"
+ }
+ }
+ else
+ {
+ Write-Log "Application file $appFile not found. Skipping app $($obj.Object.displayName)" 3
+ }
+ }
+ $dgObjects.ItemsSource = @(Get-ApplicationObjects)
+ Write-Status ""
+}
+
+function Start-DownloadAppContent
+{
+ param($obj, $path)
+ # Not use but kept for reference. File can be download but it will be encrypted
+
+ $appId = $obj.Id
+
+ $appId = "b2b79110-31f7-40bd-923b-228415c92cdb"
+
+ $appInfo = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId"
+
+ $appType = $appInfo.'@odata.type'.Trim('#')
+
+ $contentVersions = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId/$appType/contentVersions"
+
+ $contentVerId = $contentVersions.Value[0].id
+
+ $contentFiles = Invoke-GraphRequest "$($global:graphURL)/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files"
+
+ foreach($tmpFile in $contentFiles)
+ {
+ $contentFile = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files/$($tmpFile.Id)"
+ $downloadUrl = $contentFile.azureStorageUri
+ }
+}
+
+function Copy-AppPackageToIntune
+{
+ param($packageFile, $appObj)
+
+ $appType = $appObj.'@odata.type'.Trim('#')
+
+ if($appType -eq "microsoft.graph.win32LobApp")
+ {
+ Copy-Win32LOBPackage $packageFile $appObj
+ }
+ elseif($appType -eq "microsoft.graph.windowsMobileMSI")
+ {
+ Copy-MSILOB $packageFile $appObj
+ }
+ elseif($appType -eq "microsoft.graph.iosLOBApp")
+ {
+ Copy-iOSLOB $packageFile $appObj
+ }
+ elseif($appType -eq "microsoft.graph.androidLOBApp")
+ {
+ Copy-AndroidLOB $packageFile $appObj
+ }
+}
+
+#########################################################################################
+#
+# Upload file functions are based on the following scripts
+# https://github.com/microsoftgraph/powershell-intune-samples/tree/master/LOB_Application
+#
+#########################################################################################
+
+function Export-IntunewinFileObject
+{
+ param($intunewinFile, $objectName, $toFile)
+
+ Add-Type -Assembly System.IO.Compression.FileSystem
+
+ $zip = [IO.Compression.ZipFile]::OpenRead($intunewinFile)
+
+ $zip.Entries | where { $_.Name -like $objectName } | foreach {
+
+ [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $toFile, $true)
+ }
+
+ $zip.Dispose()
+}
+
+function Get-MSIFileInformation
+{
+ param($MSIFile, $Properties)
+
+ $values = @{}
+
+ try
+ {
+ $wiObj = New-Object -ComObject WindowsInstaller.Installer
+ $MSIDb = $wiObj.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $wiObj, @($MSIFile, 0))
+
+ foreach($prop in $Properties)
+ {
+ $Query = "SELECT Value FROM Property WHERE Property = '$($prop)'"
+ $View = $MSIDb.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDb, ($Query))
+ $View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null) | Out-Null
+ $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)
+ $values.Add($prop, $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1).ToString().Trim())
+ }
+
+ $MSIDb.GetType().InvokeMember("Commit", "InvokeMethod", $null, $MSIDb, $null) | Out-Null
+ $View.GetType().InvokeMember("Close", "InvokeMethod", $null, $View, $null) | Out-Null
+ $MSIDb = $null
+ $View = $null
+ }
+ catch
+ {
+ Write-Log "Failed to get MSI info from $MSIFile. $($_.Exception.Message)" 3
+ }
+ finally
+ {
+ [System.Runtime.Interopservices.Marshal]::ReleaseComObject($wiObj) | Out-Null
+ [System.GC]::Collect() | Out-Null
+ }
+
+ $values
+}
+
+function Copy-MSILOB
+{
+ param($msiFile, $appObj)
+
+ if(-not $msiFile -or (Test-Path $msiFile) -eq $false)
+ {
+ return
+ }
+
+ $appId = $appObj.Id
+ $appType = $appObj.'@odata.type'.Trim('#')
+
+ $tmpFile = [IO.Path]::GetTempFileName()
+
+ $msiInfo = Get-MSIFileInformation $msiFile @("ProductName", "ProductCode", "ProductVersion", "ProductLanguage")
+
+ if(-not $msiInfo) { return }
+
+ $fileEncryptionInfo = New-IntuneEncryptedFile $msiFile $tmpFile
+
+ [xml]$manifestXML = ''
+ $manifestXML.MobileMsiData.MsiUpgradeCode = $msiInfo["ProductCode"]
+
+ $appFileBody = @{
+ "@odata.type" = "#microsoft.graph.mobileAppContentFile"
+ name = [IO.Path]::GetFileName($msiFile)
+ size = (Get-Item $msiFile).Length
+ sizeEncrypted = (Get-Item $tmpFile).Length
+ manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestXML.OuterXml))
+ }
+
+ Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
+
+ Remove-Item $tmpFile -Force
+}
+
+function Copy-iOSLOB
+{
+ param($pkgFile, $appObj)
+
+ if(-not $pkgFile -or (Test-Path $pkgFile) -eq $false)
+ {
+ return
+ }
+
+ $appId = $appObj.Id
+ $appType = $appObj.'@odata.type'.Trim('#')
+
+ $tmpFile = [IO.Path]::GetTempFileName()
+
+ $fileEncryptionInfo = New-IntuneEncryptedFile $pkgFile $tmpFile
+
+ [string]$manifestStr = 'itemsassetskindsoftware-packageurl{UrlPlaceHolder}metadataAppRestrictionPolicyTemplate http://management.microsoft.com/PolicyTemplates/AppRestrictions/iOS/v1AppRestrictionTechnologyWindows Intune Application Restrictions Technology for iOSIntuneMAMVersionCFBundleSupportedPlatformsiPhoneOSMinimumOSVersion9.0bundle-identifierbundleidbundle-versionbundleversionkindsoftwaresubtitleLaunchMeSubtitletitlebundletitle'
+
+ $manifestStr = $manifestStr.replace("bundleid", $appObj.bundleId)
+ $manifestStr = $manifestStr.replace("bundleversion",$appObj.identityVersion)
+ $manifestStr = $manifestStr.replace("bundletitle",$appObj.$displayName)
+
+ $appFileBody = @{
+ "@odata.type" = "#microsoft.graph.mobileAppContentFile"
+ name = [IO.Path]::GetFileName($pkgFile)
+ size = (Get-Item $pkgFile).Length
+ sizeEncrypted = (Get-Item $tmpFile).Length
+ manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestStr))
+ }
+
+ Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
+
+ Remove-Item $tmpFile -Force
+}
+
+function Copy-AndroidLOB
+{
+ param($pkgFile, $appObj)
+
+ if(-not $pkgFile -or (Test-Path $pkgFile) -eq $false)
+ {
+ return
+ }
+
+ $appId = $appObj.Id
+ $appType = $appObj.'@odata.type'.Trim('#')
+
+ $tmpFile = [IO.Path]::GetTempFileName()
+
+ $fileEncryptionInfo = New-IntuneEncryptedFile $pkgFile $tmpFile
+
+ [xml]$manifestXML = 'com.leadapps.android.radio.ncp101.0.5.4A_Online_Radio_1.0.5.4.apk3'
+
+ $manifestXML.AndroidManifestProperties.Package = $appObj.identityName
+ $manifestXML.AndroidManifestProperties.PackageVersionCode = $appObj.versionCode
+ $manifestXML.AndroidManifestProperties.PackageVersionName = $appObj.versionName
+ $manifestXML.AndroidManifestProperties.ApplicationName = [IO.Path]::GetFileName($pkgFile)
+
+ $appFileBody = @{
+ "@odata.type" = "#microsoft.graph.mobileAppContentFile"
+ name = [IO.Path]::GetFileName($pkgFile)
+ size = (Get-Item $pkgFile).Length
+ sizeEncrypted = (Get-Item $tmpFile).Length
+ manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestXML.OuterXml))
+ }
+
+ Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
+
+ Remove-Item $tmpFile -Force
+}
+
+function Copy-Win32LOBPackage
+{
+ param($intunewinFile, $appObj)
+
+ if(-not $intunewinFile -or (Test-Path $intunewinFile) -eq $false)
+ {
+ return
+ }
+
+ $appId = $appObj.Id
+ $appType = $appObj.'@odata.type'.Trim('#')
+
+ #Extract the detection.xml from the intunewin file
+
+ $tmpFile = [IO.Path]::GetTempFileName()
+
+ Export-IntunewinFileObject $intunewinFile "detection.xml" $tmpFile
+
+ [xml]$DetectionXML = Get-Content $tmpFile
+
+ Remove-Item -Path $tmpFile
+
+ # Get encryption info from detection.xml and build encryptionInfo object
+
+ $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
+
+ $tmpIntunewinPath = ([IO.Path]::GetTempPath() + [Guid]::NewGuid().ToString("n"))
+ mkdir $tmpIntunewinPath | Out-Null
+ $tmpIntunewinFile = $tmpIntunewinPath + "\" + $DetectionXML.ApplicationInfo.FileName
+
+ # Extract the encrypted file from the intunewin file
+ Export-IntunewinFileObject $intunewinFile $DetectionXML.ApplicationInfo.FileName $tmpIntunewinFile
+
+ # Create mobileAppContentFile object for the file
+ $fileEncryptionInfo = @{}
+ $fileEncryptionInfo.fileEncryptionInfo = $encryptionInfo
+
+ $fileBody = @{
+ "@odata.type" = "#microsoft.graph.mobileAppContentFile"
+ name = $DetectionXML.ApplicationInfo.FileName
+ size = [int64]$DetectionXML.ApplicationInfo.UnencryptedContentSize
+ sizeEncrypted = (Get-Item $tmpIntunewinFile).Length
+ manifest = $null
+ isDependency = $false
+ }
+
+ Add-FileToIntuneApp $appId $appType $tmpIntunewinFile $fileBody
+
+ # Remove extracted inintunewin file
+ Remove-Item $tmpIntunewinPath -Force -Recurse
+}
+
+function Add-FileToIntuneApp
+{
+ param($appId, $appType, $appFile, $fileBody)
+
+ $contentVersion = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions"
+ $contentVersionId = $contentVersion.value[0].id
+ $fileObj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files" -HttpMethod POST -Content (ConvertTo-Json $fileBody -Depth 5)
+
+ if(-not $fileObj)
+ {
+ return
+ }
+
+ # Wait for Azure storage URI
+ $fileObj = Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "AzureStorageUriRequest"
+ if(-not $fileObj)
+ {
+ return
+ }
+
+ # Upload file
+ Send-IntuneFileToAzureStorage $fileObj.azureStorageUri $appFile "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)"
+
+ # Commit the file
+ $reponse = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)/commit" -HttpMethod POST -Content (ConvertTo-Json $fileEncryptionInfo -Depth 5)
+
+ Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "CommitFile"
+
+ # Commit the content version
+ $commitAppBody = @{
+ "@odata.type" = "#$appType"
+ committedContentVersion = $contentVersionId
+ }
+
+ $reponse = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId" -HttpMethod PATCH -Content (ConvertTo-Json $commitAppBody -Depth 5)
+}
+
+function Wait-IntuneFileState
+{
+ param($fileUri, $state, $maxWait = 60)
+
+ Write-Status "Wait for state $state"
+
+ $endWait = (Get-Date).AddMinutes($maxWait)
+
+ $successState = "$($state)Success"
+ $pendingState = "$($state)Pending"
+ $failedState = "$($state)Failed"
+ $timedOutState = "$($state)TimedOut"
+
+ $file = $null
+ $succes = $false
+
+ while ((Get-Date) -lt $endWait)
+ {
+ $file = Invoke-GraphRequest -Url $fileUri
+
+ if ($file.uploadState -eq $successState)
+ {
+ $succes = $true
+ break
+ }
+ elseif ($file.uploadState -ne $pendingState)
+ {
+ Write-Log "Failed to upload file. State: $($file.uploadState)" 3
+ return
+ }
+
+ Start-Sleep -s 5
+ }
+
+ if($succes -eq $false)
+ {
+ Write-Log "Wait for state operation timed out" 3
+ return
+ }
+
+ $file
+}
+
+function Send-IntuneFileToAzureStorage
+{
+ param($sasUri, $filepath, $fileUri)
+
+ try
+ {
+ $chunkSizeInBytes = 5MB
+
+ # 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-Status "Uploading file to Azure Storage`n`nUploading chunk $currentChunk of $chunks ($(($currentChunk / $chunks*100))%)"
+
+ Write-AzureStorageChunk $sasUri $id $bytes
+
+ if ($currentChunk -lt $chunks -and $sasRenewalTimer.ElapsedMilliseconds -ge 450000)
+ {
+ Request-RenewAzureStorageUpload $fileUri
+ $sasRenewalTimer.Restart()
+ }
+ }
+ $reader.Close()
+ }
+ finally
+ {
+ if ($reader -ne $null) { $reader.Dispose() }
+ }
+
+ # Finalize the upload.
+ $uploadResponse = Set-FinalizeAzureStorageUpload $sasUri $ids
+}
+
+function Request-RenewAzureStorageUpload
+{
+ param($fileUri)
+
+ $fileObj = Invoke-GraphRequest -Url "$fileUri/renewUpload" -HttpMethod POST
+
+ $file = Wait-IntuneFileState $fileUri "AzureStorageUriRenewal" $azureStorageRenewSasUriBackOffTimeInSeconds
+}
+
+function Set-FinalizeAzureStorageUpload
+{
+ param($sasUri, $ids)
+
+ $uri = "$sasUri&comp=blocklist"
+
+ if(($uri -notmatch "^http://|^https://"))
+ {
+ $uri = $global:graphURL + "/" + $uri.TrimStart('/')
+ }
+
+ $request = "PUT $uri"
+
+ $xml = ''
+ foreach ($id in $ids)
+ {
+ $xml += "$id"
+ }
+ $xml += ''
+
+ try
+ {
+ Invoke-RestMethod $uri -Method Put -Body $xml
+ }
+ catch
+ {
+ Write-Log "Failed to finilize upload. $($_.Exception.Message)" 3
+ }
+}
+
+function Write-AzureStorageChunk
+{
+ param($sasUri, $id, $body)
+
+ $uri = "$sasUri&comp=block&blockid=$id"
+
+ if(($uri -notmatch "^http://|^https://"))
+ {
+ $uri = $global:graphURL + "/" + $uri.TrimStart('/')
+ }
+
+ $request = "PUT $uri"
+
+ $iso = [System.Text.Encoding]::GetEncoding("iso-8859-1")
+ $encodedBody = $iso.GetString($body)
+ $headers = @{
+ "x-ms-blob-type" = "BlockBlob"
+ }
+
+ try
+ {
+ $response = Invoke-WebRequest $uri -Method Put -Headers $headers -Body $encodedBody
+ }
+ catch
+ {
+ Write-Log "Failed to upload file chunk. $($_.Exception.Message)" 3
+ }
+}
+
+function Get-IntuneKey
+{
+ try
+ {
+ $aes = [System.Security.Cryptography.Aes]::Create()
+ $aesProvider = New-Object System.Security.Cryptography.AesCryptoServiceProvider
+ $aesProvider.GenerateKey()
+ $aesProvider.Key
+ }
+ finally
+ {
+ if ($aesProvider -ne $null) { $aesProvider.Dispose() }
+ if ($aes -ne $null) { $aes.Dispose() }
+ }
+}
+
+function Get-IntuneKeyIV
+{
+
+ try
+ {
+ $aes = [System.Security.Cryptography.Aes]::Create()
+ $aes.IV
+ }
+ finally
+ {
+ if ($aes -ne $null) { $aes.Dispose() }
+ }
+}
+
+function Start-EncryptFileWithIV
+{
+ param($sourceFile, $targetFile, $encryptionKey, $hmacKey, $initializationVector)
+
+ $bufferBlockSize = 1024 * 4
+ $computedMac = $null
+
+ try
+ {
+ $aes = [System.Security.Cryptography.Aes]::Create()
+ $hmacSha256 = New-Object System.Security.Cryptography.HMACSHA256
+ $hmacSha256.Key = $hmacKey
+ $hmacLength = $hmacSha256.HashSize / 8
+
+ $buffer = New-Object byte[] $bufferBlockSize
+ $bytesRead = 0
+
+ $targetStream = [System.IO.File]::Open($targetFile, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read)
+ $targetStream.Write($buffer, 0, $hmacLength + $initializationVector.Length)
+
+ try
+ {
+ $encryptor = $aes.CreateEncryptor($encryptionKey, $initializationVector)
+ $sourceStream = [System.IO.File]::Open($sourceFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
+ $cryptoStream = New-Object System.Security.Cryptography.CryptoStream -ArgumentList @($targetStream, $encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
+
+ $targetStream = $null
+ while (($bytesRead = $sourceStream.Read($buffer, 0, $bufferBlockSize)) -gt 0)
+ {
+ $cryptoStream.Write($buffer, 0, $bytesRead)
+ $cryptoStream.Flush()
+ }
+ $cryptoStream.FlushFinalBlock()
+ }
+ finally
+ {
+ if ($cryptoStream -ne $null) { $cryptoStream.Dispose() }
+ if ($sourceStream -ne $null) { $sourceStream.Dispose() }
+ if ($encryptor -ne $null) { $encryptor.Dispose() }
+ }
+
+ try
+ {
+ $finalStream = [System.IO.File]::Open($targetFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::Read)
+
+ $finalStream.Seek($hmacLength, [System.IO.SeekOrigin]::Begin) > $null
+ $finalStream.Write($initializationVector, 0, $initializationVector.Length)
+ $finalStream.Seek($hmacLength, [System.IO.SeekOrigin]::Begin) > $null
+
+ $hmac = $hmacSha256.ComputeHash($finalStream)
+ $computedMac = $hmac
+
+ $finalStream.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
+ $finalStream.Write($hmac, 0, $hmac.Length)
+ }
+ finally
+ {
+ if ($finalStream -ne $null) { $finalStream.Dispose() }
+ }
+ }
+ finally
+ {
+ if ($targetStream -ne $null) { $targetStream.Dispose() }
+ if ($aes -ne $null) { $aes.Dispose() }
+ }
+
+ $computedMac
+}
+
+function New-IntuneEncryptedFile
+{
+ param($sourceFile, $targetFile)
+
+ $encryptionKey = Get-IntuneKey
+ $hmacKey = Get-IntuneKey
+ $initializationVector = Get-IntuneKeyIV
+
+ # Create the encrypted target file and compute the HMAC value.
+ $mac = Start-EncryptFileWithIV $sourceFile $targetFile $encryptionKey $hmacKey $initializationVector
+
+ # Compute the SHA256 hash of the source file and convert the result to bytes.
+ $fileDigest = (Get-FileHash $sourceFile -Algorithm SHA256).Hash
+ $fileDigestBytes = New-Object byte[] ($fileDigest.Length / 2)
+ for ($i = 0; $i -lt $fileDigest.Length; $i += 2)
+ {
+ $fileDigestBytes[$i / 2] = [System.Convert]::ToByte($fileDigest.Substring($i, 2), 16)
+ }
+
+ # Return an object that will serialize correctly to the file commit Graph API.
+ $encryptionInfo = @{}
+ $encryptionInfo.encryptionKey = [System.Convert]::ToBase64String($encryptionKey)
+ $encryptionInfo.macKey = [System.Convert]::ToBase64String($hmacKey)
+ $encryptionInfo.initializationVector = [System.Convert]::ToBase64String($initializationVector)
+ $encryptionInfo.mac = [System.Convert]::ToBase64String($mac)
+ $encryptionInfo.profileIdentifier = "ProfileVersion1"
+ $encryptionInfo.fileDigest = [System.Convert]::ToBase64String($fileDigestBytes)
+ $encryptionInfo.fileDigestAlgorithm = "SHA256"
+
+ $fileEncryptionInfo = @{}
+ $fileEncryptionInfo.fileEncryptionInfo = $encryptionInfo
+
+ $fileEncryptionInfo
+}
\ No newline at end of file
diff --git a/Extensions/AutoPilot.psm1 b/Extensions/AutoPilot.psm1
new file mode 100644
index 0000000..a0dbfb5
--- /dev/null
+++ b/Extensions/AutoPilot.psm1
@@ -0,0 +1,238 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-AutoPilotName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-AutoPilots}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-AutoPilotName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all AutoPilot policies"
+ Import-AllAutoPilotObjects (Join-Path $rootFolder (Get-AutoPilotFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-AutoPilotName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all AutoPilot policies"
+ Get-AutoPilotObjects | ForEach-Object { Export-SingleAutoPilotObject $PSItem.Object (Join-Path $rootFolder (Get-AutoPilotFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-AutoPilotFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-AutoPilotName
+{
+ (Get-AutoPilotFolderName)
+}
+
+function Get-AutoPilotFolderName
+{
+ "AutoPilot"
+}
+
+function Get-AutoPilots
+{
+ Write-Status "Loading AutoPilot profiles"
+ $dgObjects.ItemsSource = @(Get-AutoPilotObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllAutoPilots $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedAutoPilotObject $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllAutoPilotObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-AutoPilotObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles}) -copy ([scriptblock]{Copy-AutoPilot})
+}
+
+function Get-AutoPilotObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/windowsAutopilotDeploymentProfiles"
+}
+
+function Export-AllAutoPilots
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleAutoPilotObject $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedAutoPilotObject
+{
+ param($path = "$env:Temp")
+
+ Export-SingleAutoPilotObject $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleAutoPilotObject
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-AutoPilotFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+ $obj = Invoke-GraphRequest -Url "/deviceManagement/windowsAutopilotDeploymentProfiles/$($psObj.id)?`$expand=assignments"
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+
+ Add-MigrationInfo $obj.assignments
+ }
+ $global:exportedObjects++
+ }
+}
+
+function Copy-AutoPilot
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect AutoPilot item you want to copy", "Error", "OK", "Error")
+ return
+ }
+
+ $ret = Show-InputDialog "Copy AutoPilot" "Select name for the new object" "$($dgObjects.SelectedItem.displayName) Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ # Convert to Json and back to clone the object
+ $obj = ConvertTo-Json $dgObjects.SelectedItem.Object -Depth 5 | ConvertFrom-Json
+ if($obj)
+ {
+ # Import new profile
+ $obj.displayName = Remove-InvalidFileNameChars $ret
+ Import-AutoPilot $obj | Out-Null
+
+ $dgObjects.ItemsSource = @(Get-AutoPilotObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+function Import-AutoPilot
+{
+ param($obj)
+
+ Write-Status "Import $($obj.displayName)"
+
+ Invoke-GraphRequest -Url "/deviceManagement/windowsAutopilotDeploymentProfiles" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST
+}
+
+function Import-AllAutoPilotObjects
+{
+ param(
+ $path = "$env:Temp"
+ )
+
+ Import-AutoPilotObjects (Get-JsonFileObjects $path)
+}
+
+function Import-AutoPilotObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import AutoPilot profiles"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import AutoPilot profile: $($obj.Object.displayName)"
+
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+
+ $response = Import-AutoPilot $obj.Object
+
+ if($response)
+ {
+ $global:importedObjects++
+ Import-GraphAssignments2 $assignments "/deviceManagement/windowsAutopilotDeploymentProfiles/$($response.Id)/assignments"
+ }
+ }
+ $dgObjects.ItemsSource = @(Get-AutoPilotObjects)
+ Write-Status ""
+}
\ No newline at end of file
diff --git a/Extensions/AzureBranding.psm1 b/Extensions/AzureBranding.psm1
new file mode 100644
index 0000000..c92f8b8
--- /dev/null
+++ b/Extensions/AzureBranding.psm1
@@ -0,0 +1,310 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-AZBrandingName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-AZBrandings}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-AZBrandingName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all Azure branding"
+ Import-AllAZBrandingObjects (Join-Path $rootFolder (Get-AZBrandingFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-AZBrandingName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all Azure branding"
+ Get-AZBrandingObjects | ForEach-Object { Export-SingleAZBranding $PSItem.Object (Join-Path $rootFolder (Get-AZBrandingFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-AZBrandingFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-AZBrandingName
+{
+ return "Azure Branding"
+}
+
+function Get-AZBrandingFolderName
+{
+ return "AZBranding"
+}
+
+function Get-AZBrandings
+{
+ Write-Status "Loading Azure brandings"
+ $dgObjects.ItemsSource = @(Get-AZBrandingObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllAZBrandings $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedAZBranding $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("DisplayColumn", "localeDisplayName")
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllAZBrandingObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-AZBrandingObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles})
+}
+
+function Get-AZBrandingObjects
+{
+ $response = Get-AzureNativeObjects "LoginTenantBrandings" -property @('locale', 'localeDisplayName')
+ if($response)
+ {
+ $response | Where { $_.Object.isConfigured -eq $true }
+ }
+}
+
+function Export-AllAZBrandings
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleAZBranding $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedAZBranding
+{
+ param($path = "$env:Temp")
+
+ Export-SingleAZBranding $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleAZBranding
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-AZBrandingFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.localeDisplayName)"
+ $obj = Invoke-AzureNativeRequest "LoginTenantBrandings/$($psObj.locale)"
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.localeDisplayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+
+ Save-AzureBrandingFile $obj "tileLogoUrl" $path
+ Save-AzureBrandingFile $obj "bannerLogoUrl" $path
+ Save-AzureBrandingFile $obj "illustrationUrl" $path
+ Save-AzureBrandingFile $obj "squareLogoDarkUrl" $path
+
+ $global:exportedObjects++
+ }
+ Set-ObjectPath $global:txtExportPath.Text
+ }
+}
+
+function Save-AzureBrandingFile
+{
+ param($obj, $prop, $path)
+
+ if(-not $obj.$prop) { return }
+
+ $arr=$obj.$prop.Split('.')
+ if($arr.Length -ne 1)
+ {
+ return
+ }
+ $fileType = "jpg" # Assume...not OK. $arr[0] contains information about what kind of file it is
+
+ $fileName = "$path\$((Remove-InvalidFileNameChars "$($obj.localeDisplayName).$prop.$fileType"))"
+ try
+ {
+ if(Test-Path $fileName)
+ {
+ Remove-Item -Path $fileName -Force
+ }
+ [IO.File]::WriteAllBytes($fileName, [System.Convert]::FromBase64String($arr[1]))
+ }
+ catch {}
+}
+
+function Import-AZBranding
+{
+ param($obj)
+
+ if($global:runningBulkImport -eq $true)
+ {
+ # Update Default and create the rest...
+ $createNew = $obj.locale -ne 0
+ }
+ else
+ {
+ $curObj = $global:lstFiles.ItemsSource | Where { $_.Object.locale -eq $obj.locale }
+
+ if($curObj -and $obj.locale -ne 0)
+ {
+ return # Do not update existing object except default
+ }
+ elseif(-not $curObj)
+ {
+ $createNew = $true
+ }
+ else
+ {
+ $createNew = $false
+ }
+ }
+
+ $json = "{"
+
+ if($createNew) { $json += "`"locale`":`"$($obj.locale)`"," }
+
+ if($obj.signInUserIdLabel) { $json += "`"userIdLabel`": `"$($obj.signInUserIdLabel)`"," }
+ if($obj.signInPageText) { $json += "`"boilerPlateText`": `"$($obj.signInPageText)`"," }
+ if($obj.signInBackColor) { $json += "`"backgroundColor`": `"$($obj.signInBackColor)`"," }
+ if($obj.tileLogoUrl) { $json += "`"tileLogoUrl`": `"$($obj.tileLogoUrl)`"," }
+ if($obj.bannerLogoUrl) { $json += "`"bannerLogoUrl`": `"$($obj.bannerLogoUrl)`"," }
+ if($obj.illustrationUrl) { $json += "`"illustrationUrl`": `"$($obj.illustrationUrl)`"," }
+ if($obj.squareLogoDarkUrl) { $json += "`"squareLogoDarkUrl`": `"$($obj.squareLogoDarkUrl)`"," }
+
+ if($obj.hideKeepMeSignedIn -and $obj.locale -eq 0) { $json += "`"keepMeSignedInDisabled`": $($obj.hideKeepMeSignedIn.ToString().ToLower())," }
+
+ if($createNew)
+ {
+ if($curObj.bannerLogoUrl -ne $curObj.bannerLogoUrl)
+ {
+ $json += "`"isTileLogoUpdated`":true,"
+ }
+
+ if($curObj.illustrationUrl -ne $curObj.illustrationUrl)
+ {
+ $json += "`"isIllustrationImageUpdated`":true,"
+ }
+
+ if($curObj.squareLogoDarkUrl -ne $curObj.squareLogoDarkUrl)
+ {
+ $json += "`"isSquareDarkLogoUpdated`":true,"
+ }
+
+ if($curObj.bannerLogoUrl -ne $curObj.bannerLogoUrl)
+ {
+ $json += "`"isBannerLogoUpdated`":true,"
+ }
+ }
+
+ $json = $json.TrimEnd(',')
+ $json += "}"
+
+ Write-Status "Import $($obj.localeDisplayName)"
+
+ if($createNew)
+ {
+ Invoke-AzureNativeRequest "LoginTenantBrandings" -Method POST -Body $json | Out-Null
+ }
+ else
+ {
+ Invoke-AzureNativeRequest "LoginTenantBrandings/$($obj.locale)" -Method PATCH -Body $json | Out-Null
+ }
+}
+
+function Import-AllAZBrandingObjects
+{
+ param($path = "$env:Temp")
+
+ Import-AZBrandingObjects (Get-JsonFileObjects $path)
+}
+
+function Import-AZBrandingObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import Azure brandings"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import Azure branding"
+
+ $response = Import-AZBranding $obj.Object
+
+ if($response)
+ {
+ $global:importedObjects++
+ }
+ }
+ $dgObjects.ItemsSource = @(Get-AZBrandingObjects)
+ Write-Status ""
+}
\ No newline at end of file
diff --git a/Extensions/AzureNative.psm1 b/Extensions/AzureNative.psm1
new file mode 100644
index 0000000..553fd72
--- /dev/null
+++ b/Extensions/AzureNative.psm1
@@ -0,0 +1,169 @@
+#
+# Azure functions are based on: ???
+#
+function Invoke-AzureNativeRequest {
+ <#
+ .SYNOPSIS
+ Runs a command against the Azure Portal API
+ #>
+
+ [CmdletBinding(SupportsShouldProcess)]
+ param (
+ #The target of your request. This is appended to the Portal API URI. Example: Permissions
+ [Parameter(Mandatory)]$Target,
+
+ #The command you wish to execute. Example: GetUserSystemRoleTemplateIds
+ [Parameter()]$Action,
+
+ #The body of your request. This is usually in JSON format
+ $Body,
+
+ #Specify the HTTP Method you wish to use. Defaults to GET
+ [ValidateSet("GET","POST","OPTIONS","DELETE", "PATCH", "PUT")]
+ $Method = "GET",
+
+ #The base URI for the Portal API. Typically you don't need to change this
+ [Uri]$baseURI = 'https://main.iam.ad.ext.azure.com/api/',
+
+ [URI]$requestOrigin = 'https://iam.hosting.portal.azure.net',
+
+ #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
+ )
+
+ #Combine the BaseURI and Target
+ [String]$ApiAction = $Target.TrimStart('/')
+
+ if ($Action)
+ {
+ $ApiAction = $ApiAction + '/' + $Action
+ }
+
+ if($global:tokresponse -and [DateTimeOffset]::Now.ToUnixTimeSeconds() -gt $global:tokresponse.expires_on)
+ {
+ $global:tokresponse = $null
+ }
+
+ $Context = Get-AzureRmContext
+
+ if(-not $context -or -not $global:tokresponse)
+ {
+ 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 }
+ }
+
+ $InvokeRestMethodParams = @{
+ Uri = [Uri]::New($baseURI,$ApiAction)
+ Method = $Method
+ Header = [ordered]@{
+ Authorization = 'Bearer ' + $global:tokresponse.access_token
+ 'Content-Type' = 'application/json'
+ 'x-ms-client-request-id' = $requestID
+ 'Host' = $baseURI.Host
+ 'Origin' = $requestOrigin
+ }
+ Body = $Body
+ }
+
+ try
+ {
+ Invoke-RestMethod @InvokeRestMethodParams
+ if($? -eq $false)
+ {
+ throw $global:error[0]
+ }
+
+ }
+ catch
+ {
+ Write-LogError "Failed to invoke Invoke-RestMethod for Azure" $_.Exception
+ }
+}
+
+function Get-AzureNativeObjects
+{
+ param(
+ [Array]
+ $Target,
+ [Array]
+ $property,
+ [Array]
+ $exclude,
+ $SortProperty = "")
+
+ $objects = @()
+ $nativeObjects =Invoke-AzureNativeRequest $Target
+
+ if(($nativeObjects | GM -Name "items"))
+ {
+ $objectList = $nativeObjects.Items
+ }
+ else
+ {
+ $objectList = $nativeObjects
+ }
+
+ foreach($nativeObject in $objectList)
+ {
+ $params = @{}
+ if($property) { $params.Add("Property", $property) }
+ if($exclude) { $params.Add("ExcludeProperty", $exclude) }
+ foreach($objTmp in ($nativeObject | select @params))
+ {
+ $objTmp | Add-Member -NotePropertyName "Object" -NotePropertyValue $nativeObject
+ $objects += $objTmp
+ }
+ }
+
+ if($objects.Count -gt 0 -and $SortProperty -and ($objects[0] | GM -MemberType NoteProperty -Name $SortProperty))
+ {
+ $objects = $objects | sort -Property $SortProperty
+ }
+ $objects
+}
\ No newline at end of file
diff --git a/Extensions/Baseline.psm1 b/Extensions/Baseline.psm1
new file mode 100644
index 0000000..c9a0eb1
--- /dev/null
+++ b/Extensions/Baseline.psm1
@@ -0,0 +1,294 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-BaselineTemplatesName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-BaselineTemplates}
+ })
+
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-BaselineName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-BaselineProfiles}
+ })
+
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-AppProtectionName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all baseline policies"
+ Import-AllBaselineProfileObjects (Join-Path $rootFolder (Get-BaselineFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-AppProtectionName)
+ Folder = (Get-BaselineFolderName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all baseline policies"
+ Get-BaselineProfileObjects | ForEach-Object { Export-SingleBaselineProfile $PSItem.Object (Join-Path $rootFolder (Get-BaselineFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-BaselineFolderName }
+
+}
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-BaselineTemplatesName
+{
+ return "Baseline Templates"
+}
+
+
+function Get-BaselineName
+{
+ return "Baseline Profiles"
+}
+
+function Get-BaselineFolderName
+{
+ return "Baseline"
+}
+
+function Get-BaselineTemplates
+{
+ Write-Status "Loading baseline templates" -SkipLog
+ $dgObjects.ItemsSource = @(Get-BaselineTemplateObjects)
+}
+
+function Get-BaselineTemplateObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/templates"
+}
+
+function Get-BaselineProfiles
+{
+ Write-Status "Loading banding profiles" -SkipLog
+ $dgObjects.ItemsSource = @(Get-BaselineProfileObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllBaselineProfiles $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedBaselineProfile $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllBaselineProfileObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-BaselineProfileObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude @("*_Settings.json","*_assignments.json"))
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles}) -copy ([scriptblock]{Copy-BaselineProfile})
+}
+
+function Get-BaselineProfileObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/intents"
+}
+
+function Export-AllBaselineProfiles
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleBaselineProfile $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedBaselineProfile
+{
+ param($path = "$env:Temp")
+
+ Export-SingleBaselineProfile $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleBaselineProfile
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-BaselineFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+ $obj = $psObj
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+ $settings = Invoke-GraphRequest -Url "/deviceManagement/intents/$($obj.id)/settings"
+ ConvertTo-Json $settings.value -Depth 5 | Out-File "$path\$($obj.displayName)_Settings.json" -Force
+ }
+ $assignments = Invoke-GraphRequest -Url "/deviceManagement/intents/$($obj.id)/assignments"
+ if(($assignments.Value | measure).Count -gt 0)
+ {
+ ConvertTo-Json $assignments.value -Depth 5| Out-File "$path\$($obj.displayName)_assignments.json" -Force
+ }
+ $global:exportedObjects++
+ }
+}
+
+function Copy-BaselineProfile
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect baseline profile you want to copy", "Error", "OK", "Error")
+ return
+ }
+
+ $ret = Show-InputDialog "Copy baseline profiles" "Select name for the new object" "$($dgObjects.SelectedItem.displayName) - Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ # Convert to Json and back to clone the object
+ $obj = ConvertTo-Json $dgObjects.SelectedItem.Object -Depth 5 | ConvertFrom-Json
+ $settings = Invoke-GraphRequest -Url "/deviceManagement/intents/$($obj.id)/settings"
+ $intentSettings = ConvertTo-Json $settings.value -Depth 5
+
+ if($obj)
+ {
+ # Import new profile
+ $obj.displayName = $ret
+ Import-BaselineProfile $obj $intentSettings | Out-null
+
+ $dgObjects.ItemsSource = @(Get-BaselineProfileObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+function Import-BaselineProfile
+{
+ param($obj, $intentSettings, $templateId)
+
+$json = @"
+ {
+ "displayName": "$($obj.displayName)",
+ "description": "$($obj.description)",
+ "settingsDelta":
+ $($intentSettings)
+
+ }
+"@
+
+ if($templateId)
+ {
+ $tempId = $templateId
+ }
+ else
+ {
+ $tempId = $obj.templateId
+ }
+
+ Write-Status "Import $($obj.displayName)"
+
+ return Invoke-GraphRequest -Url "/deviceManagement/templates/$($tempId)/createInstance" -Content $json -HttpMethod POST
+}
+
+function Import-AllBaselineProfileObjects
+{
+ param(
+ $path = "$env:Temp"
+ )
+
+ Import-BaselineProfileObjects (Get-JsonFileObjects $path)
+}
+
+function Import-BaselineProfileObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import terms and conditions"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import security baseline: $($obj.Object.displayName)"
+
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+
+ $settingsFile = $obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_settings.json"
+ if(-not (Test-Path $settingsFile)) { continue }
+
+ $intentSettings = Get-Content $settingsFile -Raw
+
+ $response = Import-BaselineProfile $obj.Object $intentSettings
+ if($response)
+ {
+ $global:importedObjects++
+ Import-GraphAssignments $assignments "assignments" "/deviceManagement/intents/$($response.Id)/assign"
+ }
+ }
+ $dgObjects.ItemsSource = @(Get-BaselineProfileObjects)
+ Write-Status ""
+}
diff --git a/Extensions/Branding.psm1 b/Extensions/Branding.psm1
new file mode 100644
index 0000000..ec20ce4
--- /dev/null
+++ b/Extensions/Branding.psm1
@@ -0,0 +1,236 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-IntuneBrandingName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-IntuneBrandings}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-IntuneBrandingName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all Intune branding objects"
+ Import-AllIntuneBrandingObjects (Join-Path $rootFolder (Get-IntuneBrandingFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-IntuneBrandingName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all Intune branding objects"
+ Get-IntuneBrandingObjects | ForEach-Object { Export-SingleIntuneBranding $PSItem.Object (Join-Path $rootFolder (Get-IntuneBrandingFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-IntuneBrandingFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-IntuneBrandingName
+{
+ return "Intune Branding"
+}
+
+function Get-IntuneBrandingFolderName
+{
+ return "IntuneBranding"
+}
+
+function Get-IntuneBrandings
+{
+ Write-Status "Loading banding profiles"
+ $dgObjects.ItemsSource = @(Get-IntuneBrandingObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllIntuneBrandings $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ # Same as ExportAllScript since only one object is supported
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-AllIntuneBrandings $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllIntuneBrandingObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-IntuneBrandingObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles})
+}
+
+function Get-IntuneBrandingObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/intuneBrand" -property @("displayName")
+}
+
+function Export-AllIntuneBrandings
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleIntuneBranding $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SingleIntuneBranding
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-IntuneBrandingFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+
+ $obj = $psObj
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+ Save-IntuneBrandingFile $obj "lightBackgroundLogo" $path
+ Save-IntuneBrandingFile $obj "darkBackgroundLogo" $path
+ Save-IntuneBrandingFile $obj "landingPageCustomizedImage" $path
+ }
+ $global:exportedObjects++
+ }
+}
+
+function Save-IntuneBrandingFile
+{
+ param($obj, $prop, $path)
+
+ if(-not $obj.$prop.type) { return }
+
+ $arr=$obj.$prop.type.Split('/')
+ if($arr.Length -gt 1)
+ {
+ $fileType = $arr[1]
+ }
+ else
+ {
+ $fileType = ".jpg" # assume...
+ }
+
+ $fileName = "$path\$((Remove-InvalidFileNameChars "$($obj.displayName).$prop.$fileType"))"
+ try
+ {
+ if(Test-Path $fileName)
+ {
+ Remove-Item -Path $fileName -Force
+ }
+ [IO.File]::WriteAllBytes($fileName, [System.Convert]::FromBase64String($obj.$prop.value))
+ }
+ catch {}
+}
+
+
+function Import-IntuneBranding
+{
+ param($obj)
+
+ Remove-ObjectProperty $obj "@odata.context"
+
+ $newObject = @"
+{
+ "intuneBrand":$((ConvertTo-Json $obj -Depth 5))
+}
+
+"@
+ Write-Status "Import $($obj.displayName)"
+
+ # Note: Branding is imported to deviceManagement with JSON parent object intuneBrand
+ Invoke-GraphRequest -Url "$URL/deviceManagement" -Content $newObject -HttpMethod PATCH
+}
+
+function Import-AllIntuneBrandingObjects
+{
+ param($path = "$env:Temp")
+
+ Import-IntuneBrandingObjects (Get-JsonFileObjects $path)
+}
+
+function Import-IntuneBrandingObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import Intune branding"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import Intune branding"
+
+ $response = Import-IntuneBranding $obj.Object
+
+ # Note: No assignments for branding. This is default branding for everyone
+
+ }
+ $dgObjects.ItemsSource = @(Get-IntuneBrandingObjects)
+ Write-Status ""
+}
\ No newline at end of file
diff --git a/Extensions/CompliancePolicies.psm1 b/Extensions/CompliancePolicies.psm1
new file mode 100644
index 0000000..8598de9
--- /dev/null
+++ b/Extensions/CompliancePolicies.psm1
@@ -0,0 +1,244 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-CompliancePolicyName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-CompliancePolicies}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-CompliancePolicyName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all Intune compliance policies"
+ Import-AllCompliancePolicyObjects (Join-Path $rootFolder (Get-CompliancePolicyFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-CompliancePolicyName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all compliance policies"
+ Get-CompliancePolicyObjects | ForEach-Object { Export-SingleCompliancePolicy $PSItem.Object (Join-Path $rootFolder (Get-CompliancePolicyFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-CompliancePolicyFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-CompliancePolicyName
+{
+ return "Compliance Policies"
+}
+
+function Get-CompliancePolicyFolderName
+{
+ return "CompliancePolicies"
+}
+
+function Get-CompliancePolicies
+{
+ Write-Status "Loading compliance policies"
+ $dgObjects.ItemsSource = @(Get-CompliancePolicyObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllCompliancePolicies $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedCompliancePolicy $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllCompliancePolicyObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-CompliancePolicyObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles}) -copy ([scriptblock]{Copy-CompliancePolicy})
+}
+
+function Get-CompliancePolicyObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/deviceCompliancePolicies"
+}
+
+function Export-AllCompliancePolicies
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleCompliancePolicy $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedCompliancePolicy
+{
+ param($path = "$env:Temp")
+
+ Export-SingleCompliancePolicy $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleCompliancePolicy
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-CompliancePolicyFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+ $obj = Invoke-GraphRequest -Url "/deviceManagement/deviceCompliancePolicies/$($psObj.id)?`$expand=assignments"
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+
+ Add-MigrationInfo $obj.assignments
+ }
+ $global:exportedObjects++
+ }
+}
+
+function Copy-CompliancePolicy
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect compliance policy item you want to copy", "Error", "OK", "Error")
+ return
+ }
+
+ $ret = Show-InputDialog "Copy compliance policy" "Select name for the new object" "$($dgObjects.SelectedItem.displayName) - Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ # Convert to Json and back to clone the object
+ $obj = ConvertTo-Json $dgObjects.SelectedItem.Object -Depth 5 | ConvertFrom-Json
+ if($obj)
+ {
+ # Import new profile
+ $obj.displayName = $ret
+ Import-CompliancePolicy $obj | Out-null
+
+ $dgObjects.ItemsSource = @(Get-CompliancePolicyObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+function Import-CompliancePolicy
+{
+ param($obj)
+
+ $json = ConvertTo-Json $obj -Depth 5
+ $json = $json.Trim().TrimEnd('}').Trim()
+ $json += @"
+,
+ "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":"","notificationMessageCCList":[]}]}]
+}
+
+"@
+
+ Write-Status "Import $($obj.displayName)"
+
+ Invoke-GraphRequest -Url "/deviceManagement/deviceCompliancePolicies" -Content $json -HttpMethod POST
+}
+
+function Import-AllCompliancePolicyObjects
+{
+ param($path = "$env:Temp")
+
+ Import-CompliancePolicyObjects (Get-JsonFileObjects $path)
+}
+
+function Import-CompliancePolicyObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import compliance policies"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import Compliance Policy: $($obj.Object.displayName)"
+
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+
+ $response = Import-CompliancePolicy $obj.Object
+ if($response)
+ {
+ $global:importedObjects++
+ Import-GraphAssignments $assignments "assignments" "/deviceManagement/deviceCompliancePolicies/$($response.Id)/assign"
+ }
+ }
+ $dgObjects.ItemsSource = @(Get-CompliancePolicyObjects)
+ Write-Status ""
+}
\ No newline at end of file
diff --git a/Extensions/ConditionalAccess.psm1 b/Extensions/ConditionalAccess.psm1
new file mode 100644
index 0000000..c8aabd7
--- /dev/null
+++ b/Extensions/ConditionalAccess.psm1
@@ -0,0 +1,273 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-ConditionalAccessName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-ConditionalAccess}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-ConditionalAccessName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all conditional access policies"
+ Import-AllConditionalAccessObjects (Join-Path $rootFolder (Get-ConditionalAccessFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-ConditionalAccessName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all conditional access policies"
+ Get-ConditionalAccessObjects | ForEach-Object { Export-SingleConditionalAccess $PSItem.Object (Join-Path $rootFolder (Get-ConditionalAccessFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-ConditionalAccessFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-ConditionalAccessName
+{
+ return "Conditional Access"
+}
+
+function Get-ConditionalAccessFolderName
+{
+ return "ConditionalAccess"
+}
+
+function Get-ConditionalAccess
+{
+ Write-Status "Loading conditional access objects"
+ $dgObjects.ItemsSource = @(Get-ConditionalAccessObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllConditionalAccess $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedConditionalAccess $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllConditionalAccessObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-ConditionalAccessObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles})
+}
+
+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')
+}
+
+function Export-AllConditionalAccess
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleConditionalAccess $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedConditionalAccess
+{
+ param($path = "$env:Temp")
+
+ Export-SingleConditionalAccess $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleConditionalAccess
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-ConditionalAccessFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.policyName)"
+
+ if($psObj.baselineType -eq 0)
+ {
+ $obj = Invoke-AzureNativeRequest "Policies/$($psObj.policyId)"
+ }
+ else
+ {
+ $obj = Invoke-AzureNativeRequest "BaselinePolicies/$($psObj.policyId)"
+ }
+
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $psObj.policyName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+ }
+
+ if($jsonObj.usersV2.included.groupIds)
+ {
+ $jsonObj.usersV2.included.groupIds | ForEach-Object { Add-GroupMigrationObject $PSItem }
+ }
+
+ if($jsonObj.usersV2.excluded.groupIds)
+ {
+ $jsonObj.usersV2.excluded.groupIds | ForEach-Object { Add-GroupMigrationObject $PSItem }
+ }
+
+ if($jsonObj.usersV2.included.userIds -or $jsonObj.usersV2.excluded.userIds)
+ {
+ Write-Log "Users are specified in $($psObj.policyName). User are not supported in this version. This conditional access policy might not be imported" 2
+ }
+
+ if($jsonObj.usersV2.included.roleIds -or $jsonObj.usersV2.excluded.roleIds)
+ {
+ Write-Log "Roles are specified in $($psObj.policyName). Roles are not supported in this version. This conditional access policy might not be imported" 2
+ }
+
+ if($jsonObj.conditions.namedNetworks.includedNetworkIds -or $jsonObj.conditions.namedNetworks.excludedNetworkIds)
+ {
+ Write-Log "Networks are specified in $($psObj.policyName). Named networks are not supported in this version. This conditional access policy might not be imported" 2
+ }
+
+ # There might be a lot more to check here...
+
+ $global:exportedObjects++
+ }
+}
+
+function Import-ConditionalAccess
+{
+ param($obj)
+
+ $json = Update-JsonForEnvironment $json
+
+ if($obj.baselineType -eq 0)
+ {
+ $obj.policyId = ""
+ $obj.isAllProtocolsEnabled = $true
+ $json = ConvertTo-Json $obj -Depth 10
+ $json = Update-JsonForEnvironment $json
+
+ if((Invoke-AzureNativeRequest "Policies/Validate" -Method POST -Body $json) -eq 11)
+ {
+ Invoke-AzureNativeRequest "Policies" -Method POST -Body $json | Out-Null
+ }
+ else
+ {
+ Write-Log "Policy validation of json data failed" 3
+ }
+ }
+ else
+ {
+ Write-Log "Conditional Access Baseline Policies does not support import"
+ #Invoke-AzureNativeRequest "BaselinePolicies/$($obj.id)" -Method PUT -Body (ConvertTo-Json $obj -Depth 5) | Out-Null
+ }
+}
+
+function Import-AllConditionalAccessObjects
+{
+ param($path = "$env:Temp")
+
+ Import-ConditionalAccessObjects (Get-JsonFileObjects $path)
+}
+
+function Import-ConditionalAccessObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import conditional access policies"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import Conditional Access: $($obj.Object.policyName)"
+
+ $response = Import-ConditionalAccess $obj.Object
+
+ if($response)
+ {
+ $global:importedObjects++
+ }
+ # No additionl assignments on conditional access policies
+ }
+ $dgObjects.ItemsSource = @(Get-ConditionalAccessObjects)
+ Write-Status ""
+}
+
+<#
+ # Get all networks
+ Get-AzureNativeObjects "NamedNetworksV2"
+
+ # Network example
+ #{"networkName":"Australia","cidrIpRanges":[],"categories":[],"applyToUnknownCountry":false,"countryIsoCodes":["AU"],"isTrustedLocation":false,"namedLocationsType":2}
+
+ Get-AzureNativeObjects "NamedNetworksV2" -Method POST -Body $json | Out-Nul
+
+ # Get all contry codes
+ NamedNetworksV2/CountryCodes
+#>
\ No newline at end of file
diff --git a/Extensions/ConfigurationItems.psm1 b/Extensions/ConfigurationItems.psm1
new file mode 100644
index 0000000..23543d0
--- /dev/null
+++ b/Extensions/ConfigurationItems.psm1
@@ -0,0 +1,246 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-DeviceConfigurationName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-DeviceConfigurations}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-DeviceConfigurationName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all device configuration objects"
+ Import-AllDeviceConfigurationObjects (Join-Path $rootFolder (Get-DeviceConfigurationFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-DeviceConfigurationName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all device configuration objects"
+ Get-DeviceConfigurationObjects | ForEach-Object { Export-SingleDeviceConfiguration $PSItem.Object (Join-Path $rootFolder (Get-DeviceConfigurationFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-DeviceConfigurationFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-DeviceConfigurationName
+{
+ return "Device Configurations"
+}
+
+function Get-DeviceConfigurationFolderName
+{
+ return "DeviceConfigurations"
+}
+
+function Get-DeviceConfigurations
+{
+ Write-Status "Loading device configurations"
+ $dgObjects.ItemsSource = @(Get-DeviceConfigurationObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllDeviceConfigurations $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedDeviceConfiguration $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllDeviceConfigurationObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-DeviceConfigurationObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles}) -copy ([scriptblock]{Copy-DeviceConfiguration})
+}
+
+function Get-DeviceConfigurationObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/deviceConfigurations"#,"/deviceManagement/groupPolicyConfigurations"
+}
+
+function Export-AllDeviceConfigurations
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleDeviceConfiguration $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedDeviceConfiguration
+{
+ param($path = "$env:Temp")
+
+ Export-SingleDeviceConfiguration $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleDeviceConfiguration
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-DeviceConfigurationFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+ $obj = Invoke-GraphRequest -Url "/deviceManagement/deviceConfigurations/$($psObj.id)?`$expand=assignments"
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+ if($script:chkExportScript.IsChecked)
+ {
+ $fileName = "$path\$($obj.FileName)"
+ [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($obj.scriptContent)) | Out-File $fileName -Force
+ }
+
+ Add-MigrationInfo $obj.assignments
+
+ $global:exportedObjects++
+ }
+ }
+}
+
+function Copy-DeviceConfiguration
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect device configuration item you want to copy", "Error", "OK", "Error")
+ return
+ }
+
+ $ret = Show-InputDialog "Copy device configuration" "Select name for the new object" "$($dgObjects.SelectedItem.displayName) - Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ # Convert to Json and back to clone the object
+ $obj = ConvertTo-Json $dgObjects.SelectedItem.Object -Depth 5 | ConvertFrom-Json
+ if($obj)
+ {
+ # Import new profile
+ $obj.displayName = $ret
+ Import-DeviceConfiguration $obj | Out-Null
+
+ $dgObjects.ItemsSource = @(Get-DeviceConfigurationObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+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)"
+
+ Invoke-GraphRequest -Url "/deviceManagement/deviceConfigurations" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST
+}
+
+function Import-AllDeviceConfigurationObjects
+{
+ param($path = "$env:Temp")
+
+ Import-DeviceConfigurationObjects (Get-JsonFileObjects $path)
+}
+
+function Import-DeviceConfigurationObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import device configuration profiles"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import device configuration policy: $($obj.Object.displayName)"
+
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+
+ $response = Import-DeviceConfiguration $obj.Object
+ if($response)
+ {
+ $global:importedObjects++
+ Import-GraphAssignments $assignments "assignments" "/deviceManagement/deviceConfigurations/$($response.Id)/assign"
+ }
+ }
+ $dgObjects.ItemsSource = @(Get-DeviceConfigurationObjects)
+ Write-Status ""
+}
\ No newline at end of file
diff --git a/Extensions/EnrollmentStatusPage.psm1 b/Extensions/EnrollmentStatusPage.psm1
new file mode 100644
index 0000000..b8e7a57
--- /dev/null
+++ b/Extensions/EnrollmentStatusPage.psm1
@@ -0,0 +1,277 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-ESPName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-ESPs}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-ESPName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all enrollment status page settings"
+ Import-AllESPObjects (Join-Path $rootFolder (Get-ESPFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-ESPName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all enrollment status page settings"
+ Get-ESPObjects | ForEach-Object { Export-SingleESP $PSItem.Object (Join-Path $rootFolder (Get-ESPFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-ESPFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-ESPName
+{
+ return "Enrollment Status Page"
+}
+
+
+function Get-ESPFolderName
+{
+ return "EnrollmentStatusPage"
+}
+
+function Get-ESPs
+{
+ Write-Status "Loading enrollment status page objects"
+ $dgObjects.ItemsSource = @(Get-ESPObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllESPs $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedESP $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllESPObjects $global:txtExportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-ESPObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles}) -copy ([scriptblock]{Copy-ESP})
+}
+
+function Get-ESPObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/deviceEnrollmentConfigurations"
+}
+
+function Export-AllESPs
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleESP $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedESP
+{
+ param($path = "$env:Temp")
+
+ Export-SingleESP $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleESP
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-ESPFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+ $obj = Invoke-GraphRequest -Url "/deviceManagement/deviceEnrollmentConfigurations/$($psObj.id)" #?`$expand=assignments"
+ if($obj)
+ {
+ if($obj.id -like "*_default*")
+ {
+ $idx = $obj.id.ToLower().IndexOf("_default")
+ $baseName = "Default_" + $obj.id.SubString($idx + "_default".Length)
+ }
+ else
+ {
+ # ?`$expand=assignments is not working so get assignments
+ $assignments = Invoke-GraphRequest -Url "/deviceManagement/deviceEnrollmentConfigurations/$($obj.id)/assignments"
+ if($assignments.value)
+ {
+ $obj | Add-Member -NotePropertyName "assignments" -NotePropertyValue $assignments.value
+ }
+ $baseName = Remove-InvalidFileNameChars $obj.displayName
+ }
+ $fileName = "$path\$baseName.json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+
+ Add-MigrationInfo $obj.assignments
+ }
+ $global:exportedObjects++
+ }
+}
+
+function Copy-ESP
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect enrollment status page item you want to copy", "Error", "OK", "Error") | Out-Null
+ return
+ }
+
+ if($dgObjects.SelectedItem.Object.id -like "*_default*")
+ {
+ [System.Windows.MessageBox]::Show("You cannot copy default items`n`nSelect custom entrollment status page item", "Error", "OK", "Error") | Out-Null
+ return
+ }
+
+ $ret = Show-InputDialog "Copy enrollment status page" "Select name for the new object" "$($dgObjects.SelectedItem.displayName) - Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ # Convert to Json and back to clone the object
+ $obj = ConvertTo-Json $dgObjects.SelectedItem.Object -Depth 5 | ConvertFrom-Json
+ if($obj)
+ {
+ # Import new profile
+ $obj.displayName = $ret
+ Import-ESP $obj | Out-Null
+
+ $dgObjects.ItemsSource = @(Get-ESPObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+function Import-ESP
+{
+ param($obj)
+
+ if($obj.id -like "*_default*")
+ {
+ Write-Status "Update $($obj.displayName)"
+
+ Invoke-GraphRequest -Url "/deviceManagement/deviceEnrollmentConfigurations/$($obj.id)" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod PATCH
+ }
+ else
+ {
+ Write-Status "Import $($obj.displayName)"
+
+ Invoke-GraphRequest -Url "/deviceManagement/deviceEnrollmentConfigurations" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST
+ }
+}
+
+function Import-AllESPObjects
+{
+ param($path = "$env:Temp")
+
+ Import-ESPObjects (Get-JsonFileObjects $path)
+}
+
+function Import-ESPObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import enrollment status page"
+
+ foreach($obj in $Objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ if($obj.Object.id -like "*_default*")
+ {
+ $idx = $obj.Object.id.ToLower().IndexOf("_default")
+ $extInfo = " ($($obj.Object.id.SubString($idx + "_default".Length)))"
+ }
+ else
+ {
+ $extInfo = ""
+ }
+
+ Write-Log "Import Enrollment Status Page: $($obj.Object.displayName)$extInfo"
+
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+
+ $response = Import-ESP $obj.Object
+
+ if($response)
+ {
+ $global:importedObjects++
+ Import-GraphAssignments $assignments "enrollmentConfigurationAssignments" "/deviceManagement/deviceEnrollmentConfigurations/$($response.Id)/assign"
+ }
+ }
+
+ $dgObjects.ItemsSource = @(Get-ESPObjects)
+ Write-Status ""
+}
\ No newline at end of file
diff --git a/Extensions/GroupPolicy.psm1 b/Extensions/GroupPolicy.psm1
new file mode 100644
index 0000000..c61b7e4
--- /dev/null
+++ b/Extensions/GroupPolicy.psm1
@@ -0,0 +1,322 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-GPOSettingName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-GPOSettings}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-GPOSettingName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all administrative templates"
+ Import-AllGPOSettingObjects (Join-Path $rootFolder (Get-GPOSettingFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-GPOSettingName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all administrative templates"
+ Get-GPOSettingObjects | ForEach-Object { Export-SingleGPOSetting $PSItem.Object (Join-Path $rootFolder (Get-GPOSettingFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-GPOSettingFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-GPOSettingName
+{
+ return "Administrative Templates"
+}
+
+
+function Get-GPOSettingFolderName
+{
+ return "AdministrativeTemplates"
+}
+
+function Get-GPOSettings
+{
+ Write-Status "Loading administrative templates"
+ $dgObjects.ItemsSource = @(Get-GPOSettingObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllGPOSettings $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedGPOSetting $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllGPOSettingObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-GPOSettingObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles}) -copy ([scriptblock]{Copy-GPOSetting})
+}
+
+function Get-GPOSettingObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/groupPolicyConfigurations"
+}
+
+function Export-AllGPOSettings
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleGPOSetting $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedGPOSetting
+{
+ param($path = "$env:Temp")
+
+ Export-SingleGPOSetting $global:dgObjects.SelectedItem.Object $path
+}
+
+function Get-GPOObjectSettings
+{
+ param($GPOObj)
+
+ $gpoSettings = @()
+
+ # Get all configured policies in the Administrative Templates profile
+ $GPODefinitionValues = Invoke-GraphRequest -Url "/deviceManagement/groupPolicyConfigurations/$($GPOObj.id)/definitionValues?`$expand=definition"
+ foreach($definitionValue in $GPODefinitionValues.value)
+ {
+ # Get presentation values for the current settings (with presentation object included)
+ $presentationValues = Invoke-GraphRequest -Url "/deviceManagement/groupPolicyConfigurations/$($GPOObj.id)/definitionValues/$($definitionValue.id)/presentationValues?`$expand=presentation"
+
+ # Set base policy settings
+ $obj = @{
+ "enabled" = $definitionValue.enabled
+ "definition@odata.bind" = "$($global:graphURL)/deviceManagement/groupPolicyDefinitions('$($definitionValue.definition.id)')"
+ }
+
+ if($presentationValues.value)
+ {
+ # Policy presentation values set e.g. a drop down list, check box, text box etc.
+ $obj.presentationValues = @()
+
+ $presentations = $null
+ foreach ($presentationValue in $presentationValues.value)
+ {
+ # Add presentation@odata.bind property that links the value to the presentation object
+ $presentationValue | Add-Member -MemberType NoteProperty -Name "presentation@odata.bind" -Value "$($global:graphURL)/deviceManagement/groupPolicyDefinitions('$($definitionValue.definition.id)')/presentations('$($presentationValue.presentation.id)')"
+
+ #Remove presentation object so it is not included in the export
+ Remove-ObjectProperty $presentationValue "presentation"
+
+ #Optional removes. Import will igonre them
+ Remove-ObjectProperty $presentationValue "id"
+ Remove-ObjectProperty $presentationValue "lastModifiedDateTime"
+ Remove-ObjectProperty $presentationValue "createdDateTime"
+
+ # Add presentation value to the list
+ $obj.presentationValues += $presentationValue
+ }
+ }
+ $gpoSettings += $obj
+ }
+ $gpoSettings
+}
+
+function Export-SingleGPOSetting
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-GPOSettingFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+ $obj = Invoke-GraphRequest -Url "deviceManagement/groupPolicyConfigurations/$($psObj.Id)?`$expand=assignments"
+
+ if($obj)
+ {
+ # Save Administrative Templates profile
+ ConvertTo-Json $obj -Depth 5 | Out-File "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json" -Force
+
+ # Collect and save all the settings of the Administrative Templates profile
+ $gpoSettings = Get-GPOObjectSettings $obj
+ ConvertTo-Json $gpoSettings -Depth 5 | Out-File "$path\$($obj.displayName)_Settings.json" -Force
+
+ # Export assignment info
+ Add-MigrationInfo $obj.assignments
+ }
+ $global:exportedObjects++
+ }
+}
+
+function Copy-GPOSetting
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect administrative templates profile you want to copy", "Error", "OK", "Error")
+ return
+ }
+
+ $ret = Show-InputDialog "Copy administrative template" "Select name for the new profile" "$($dgObjects.SelectedItem.displayName) - Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ # Convert to Json and back to clone the object
+ $obj = ConvertTo-Json $dgObjects.SelectedItem.Object -Depth 5 | ConvertFrom-Json
+ if($obj)
+ {
+ # Get the settings of the profile
+ $gpoSettings = Get-GPOObjectSettings $obj
+
+ # Import the new profile
+ $obj.displayName = $ret
+ Import-GPOSetting $obj $gpoSettings | Out-Null
+
+ #Reload objects
+ $dgObjects.ItemsSource = @(Get-GPOSettingObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+function Import-GPOSetting
+{
+ param($obj, $settings)
+
+ Write-Status "Import $($obj.displayName)"
+
+ # Import Administrative Template profile
+ $response = Invoke-GraphRequest -Url "/deviceManagement/groupPolicyConfigurations" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST
+
+ if($response)
+ {
+ foreach($setting in $settings)
+ {
+ # 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
+ }
+ }
+
+ $response
+}
+
+function Import-AllGPOSettingObjects
+{
+ param($path = "$env:Temp")
+
+ # Read json files and import all objects
+ # Note: Each json file must match the object type being imported
+ Import-GPOSettingObjects (Get-JsonFileObjects $path)
+}
+
+function Import-GPOSettingObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import administrative template profile"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import Administrative Template: $($obj.Object.displayName)"
+
+ $gpoSettings = $null
+
+ # Load settings from the _settings.json file
+ $settingsFile = ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_settings.json")
+ if(Test-Path $settingsFile)
+ {
+ $gpoSettings = (ConvertFrom-Json (Get-Content $settingsFile -Raw))
+ }
+
+ # Get assignment settings
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+
+ # Import Administrative Template object
+ $response = Import-GPOSetting $obj.Object $gpoSettings
+
+ if($response)
+ {
+ $global:importedObjects++
+ # Import assignments
+ Import-GraphAssignments $assignments "assignments" "/deviceManagement/groupPolicyConfigurations/$($response.Id)/assign"
+ }
+ }
+
+ #Reload list of objects
+ $dgObjects.ItemsSource = @(Get-GPOSettingObjects)
+ Write-Status ""
+}
\ No newline at end of file
diff --git a/Extensions/MDM_MAM.psm1 b/Extensions/MDM_MAM.psm1
new file mode 100644
index 0000000..3b80ab5
--- /dev/null
+++ b/Extensions/MDM_MAM.psm1
@@ -0,0 +1,215 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-MDMMAMName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-MDMMAM}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-MDMMAMName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all MDM/MAM setting"
+ Import-AllMDMMAMObjects (Join-Path $rootFolder (Get-MDMMAMFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-MDMMAMName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all MDM/MAM settings"
+ Get-MDMMAMObjects | ForEach-Object { Export-SingleMDMMAM $PSItem.Object (Join-Path $rootFolder (Get-MDMMAMFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-MDMMAMFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-MDMMAMName
+{
+ return "MDM/MAM"
+}
+
+function Get-MDMMAMFolderName
+{
+ return "MDMMAM"
+}
+
+function Get-MDMMAM
+{
+ Write-Status "Loading MDM/MAM object"
+ $dgObjects.ItemsSource = @(Get-MDMMAMObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllMDMMAM $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedMDMMAM $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllMDMMAMObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-MDMMAMObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles})
+}
+
+function Get-MDMMAMObjects
+{
+ Get-AzureNativeObjects "MdmApplications" -property @('appDisplayName')
+}
+
+function Export-AllMDMMAM
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleMDMMAM $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedMDMMAM
+{
+ param($path = "$env:Temp")
+
+ Export-SingleMDMMAM $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleMDMMAM
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-MDMMAMFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.appDisplayName)"
+
+ $obj = Invoke-AzureNativeRequest "MdmApplications/$($psObj.objectId)"
+
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.appDisplayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+ }
+
+ if($obj.mdmAppliesToGroups)
+ {
+ $obj.mdmAppliesToGroups | ForEach-Object { Add-GroupMigrationObject $PSItem.objectId }
+ }
+
+ if($obj.mamAppliesToGroups)
+ {
+ $obj.mamAppliesToGroups | ForEach-Object { Add-GroupMigrationObject $PSItem.objectId }
+ }
+
+ $global:exportedObjects++
+ }
+}
+
+function Import-MDMMAM
+{
+ param($obj)
+
+ $argStr = "?"
+ if($obj.enrollmentUrl) { $argStr += "mdmAppliesToChanged=true" }
+ else{ $argStr += "mdmAppliesToChanged=false" }
+ if($obj.mamEnrollmentUrl) { $argStr += "&mamAppliesToChanged=true" }
+ else{ $argStr += "&mamAppliesToChanged=false" }
+
+ $response = Invoke-AzureNativeRequest "MdmApplications/$($obj.objectId)$argStr" -Method PUT -Body (Update-JsonForEnvironment (ConvertTo-Json $obj -Depth 5))
+}
+
+function Import-AllMDMMAMObjects
+{
+ param($path = "$env:Temp")
+
+ Import-MDMMAMObjects (Get-JsonFileObjects $path)
+}
+
+function Import-MDMMAMObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import MDM/MAM settings"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import MDM/MAM app settings: $($obj.Object.appDisplayName)"
+
+ Import-MDMMAM $obj.Object
+
+ # No assignments for MDM/MAM
+ }
+ $dgObjects.ItemsSource = @(Get-MDMMAMObjects)
+ Write-Status ""
+}
\ No newline at end of file
diff --git a/Extensions/MSGraphIntune.psm1 b/Extensions/MSGraphIntune.psm1
new file mode 100644
index 0000000..a1c66ed
--- /dev/null
+++ b/Extensions/MSGraphIntune.psm1
@@ -0,0 +1,1438 @@
+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 (Nothing will work)`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))
+ {
+ [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")
+ {
+ exit
+ }
+ }
+
+ if(-not $global:authentication)
+ {
+ if((Get-Command Connect-MSGraph))
+ {
+ $global:authentication = Connect-MSGraph -PassThru
+ }
+ }
+
+ $global:graphURL = "https://graph.microsoft.com/beta"
+
+# Add settings
+ $global:appSettingSections += (New-Object PSObject -Property @{
+ Title = "Intune"
+ Id = "IntuneAzure"
+ Values = @()
+ })
+
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Root folder"
+ Key = "IntuneRootFolder"
+ Type = "Folder"
+ }) "IntuneAzure"
+
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "App packages folder"
+ Key = "IntuneAppPackages"
+ Type = "Folder"
+ }) "IntuneAzure"
+
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Add object type"
+ Key = "AddObjectType"
+ Type = "Boolean"
+ DefaultValue = $true
+ }) "IntuneAzure"
+
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Add company name"
+ Key = "AddCompanyName"
+ Type = "Boolean"
+ DefaultValue = $true
+ }) "IntuneAzure"
+
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Export Assignments"
+ Key = "ExportIntuneAssignments"
+ Type = "Boolean"
+ DefaultValue = $true
+ }) "IntuneAzure"
+
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Create groups"
+ Key = "CreateIntuneGroupOnImport"
+ Type = "Boolean"
+ DefaultValue = $true
+ }) "IntuneAzure"
+
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Convert synced groups"
+ Key = "ConvertIntuneSyncedGroupOnImport"
+ Type = "Boolean"
+ DefaultValue = $true
+ }) "IntuneAzure"
+
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Import Assignments"
+ Key = "ImportAssignments"
+ Type = "Boolean"
+ DefaultValue = $true
+ }) "IntuneAzure"
+
+
+ #Add menu group and items
+ Add-MenuSection (New-Object PSObject -Property @{ Title = "Intune/Azure Objects"; ID="IntuneGraphAPI"; Order = 10})
+ Add-MenuSection (New-Object PSObject -Property @{ Title = "Intune/Azure Management"; ID="IntuneGraphAPIEX"; Order = 20})
+
+ # Add default menu items
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = 'Bulk Import'
+ MenuID = "IntuneGraphAPIEX"
+ Script = [ScriptBlock]{ Show-ImportAllForm }
+ })
+
+ # Add default menu items
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = 'Bulk Export'
+ MenuID = "IntuneGraphAPIEX"
+ Script = [ScriptBlock]{ Show-ExportAllForm }
+ })
+
+ $global:Me = Invoke-GraphRequest "ME"
+ $global:Organization = (Invoke-GraphRequest "Organization").Value
+
+ $global:UpdateJsonForMigration = $true
+}
+
+function Show-ExportAllForm
+{
+ param($Extension)
+
+$xmlStr = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $($Extension.Xaml)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"@
+ $exportGrid = [System.Windows.Markup.XamlReader]::Parse($xmlStr)
+
+ $btnDoExport = $exportGrid.FindName("btnDoExport")
+ $btnCancel = $exportGrid.FindName("btnCancel")
+ $script:lstObjectsToExport = $exportGrid.FindName("lstObjectsToExport")
+ $global:txtExportPath = $exportGrid.FindName("txtExportPath")
+ $global:chkAddCompanyName = $exportGrid.FindName("chkAddCompanyName")
+ $global:chkAddObjectType = $exportGrid.FindName("chkAddObjectType")
+ $global:chkCheckAll = $exportGrid.FindName("chkCheckAll")
+
+ $script:btnBrowse = $exportGrid.FindName("browseExportPath")
+ $btnBrowse.Tag = $global:txtExportPath
+ $btnBrowse.Add_Click({
+ $folder = Get-Folder $this.Tag.Text
+ if($folder) { $this.Tag.Text = $folder }
+ })
+
+ $global:exportObjects = @()
+ Invoke-ModuleFunction "Get-SupportedExportObjects"
+
+ $script:lstObjectsToExport.ItemsSource = $global:exportObjects
+
+ if($Extension.Script)
+ {
+ Invoke-Command -ScriptBlock $Extension.Script -ArgumentList $exportGrid
+ }
+
+ $global:chkCheckAll.Add_Click({
+ foreach($obj in $global:exportObjects)
+ {
+ $obj.Selected = $global:chkCheckAll.IsChecked
+ }
+ $script:lstObjectsToExport.Items.Refresh()
+ })
+
+ $btnDoExport.Add_Click({
+ if([System.Windows.MessageBox]::Show("Are you sure you want to export all selected objects?", "Start bulk export?", "YesNo", "Question") -eq "Yes")
+ {
+ Invoke-GraphExportAll $global:txtExportPath.Text
+ Write-Status ""
+ if($global:exportedObjects -gt 0)
+ {
+ [System.Windows.MessageBox]::Show("$($global:exportedObjects) objects exported", "Export finished", "OK", "Info")
+ }
+ else
+ {
+ [System.Windows.MessageBox]::Show("No objects was exported!", "Export finished", "OK", "Warning")
+ }
+ }
+ })
+
+ Set-ObjectGrid $exportGrid
+}
+
+
+function Show-ImportAllForm
+{
+ param($Extension)
+
+$xmlStr = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $($Extension.Xaml)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"@
+ $importGrid = [System.Windows.Markup.XamlReader]::Parse($xmlStr)
+
+ $btnDoImport = $importGrid.FindName("btnDoImport")
+ $btnCancel = $importGrid.FindName("btnCancel")
+ $script:lstObjectsToImport = $importGrid.FindName("lstObjectsToImport")
+ $global:txtImportPath = $importGrid.FindName("txtImportPath")
+ $global:chkAddCompanyName = $importGrid.FindName("chkAddCompanyName")
+ $global:chkAddObjectType = $importGrid.FindName("chkAddObjectType")
+ $global:chkCheckAll = $importGrid.FindName("chkCheckAll")
+ $global:chkImportAssignments = $importGrid.FindName("chkImportAssignments")
+
+ $script:btnBrowse = $importGrid.FindName("browseImportPath")
+ $btnBrowse.Tag = $global:txtImportPath
+ $btnBrowse.Add_Click({
+ $folder = Get-Folder $this.Tag.Text
+ if($folder) { $this.Tag.Text = $folder }
+ })
+
+ $global:importObjects = @()
+
+ Invoke-ModuleFunction "Get-SupportedImportObjects"
+
+ $script:lstObjectsToImport.ItemsSource = $global:importObjects
+
+ if($Extension.Script)
+ {
+ Invoke-Command -ScriptBlock $Extension.Script -ArgumentList $importGrid
+ }
+
+ $global:chkCheckAll.Add_Click({
+ foreach($obj in $global:importObjects)
+ {
+ $obj.Selected = $global:chkCheckAll.IsChecked
+ }
+ $script:lstObjectsToImport.Items.Refresh()
+ })
+
+ $btnDoImport.Add_Click({
+ if([System.Windows.MessageBox]::Show("Are you sure you want to import all selected objects?", "Start bulk import?", "YesNo", "Question") -eq "Yes")
+ {
+ Invoke-GraphImportAll $global:txtImportPath.Text
+ Write-Status ""
+ if($global:importedObjects -gt 0)
+ {
+ [System.Windows.MessageBox]::Show("$($global:importedObjects) objects imported", "Import finished", "OK", "Info")
+ }
+ else
+ {
+ [System.Windows.MessageBox]::Show("No objects was imported!", "Import finished", "OK", "Warning")
+ }
+ }
+ })
+
+ Set-ObjectGrid $importGrid
+}
+
+function Invoke-GraphRequest
+{
+ param (
+ [Parameter(Mandatory)]
+ $Url,
+
+ $Content,
+
+ $Headers,
+
+ [ValidateSet("GET","POST","OPTIONS","DELETE", "PATCH")]
+ $HttpMethod = "GET"
+ )
+
+ $params = @{}
+
+ if($Content) { $params.Add("Content", $Content) }
+ if($Headers) { $params.Add("Headers", $Headers) }
+
+ if(($Url -notmatch "^http://|^https://"))
+ {
+ $Url = $global:graphURL + "/" + $Url.TrimStart('/')
+ }
+
+ try
+ {
+ Invoke-MSGraphRequest -Url $Url -HttpMethod $HttpMethod.ToUpper() @params -ErrorAction SilentlyContinue
+ if($? -eq $false)
+ {
+ throw $global:error[0]
+ }
+
+ }
+ catch
+ {
+ Write-LogError "Failed to invoke MSGraphRequest" $_.Exception
+ }
+}
+
+function Get-GraphObjects
+{
+ param(
+ [Array]
+ $Url,
+ [Array]
+ $property = @('displayName', 'description', 'id'),
+ [Array]
+ $exclude,
+ $SortProperty = "displayName")
+
+ $objects = @()
+
+ $graphObjects = Invoke-GraphRequest -Url $url
+
+ if(($graphObjects | GM -Name Value -MemberType NoteProperty))
+ {
+ $retObjects = $graphObjects.Value
+ }
+ else
+ {
+ $retObjects = $graphObjects
+ }
+
+ foreach($graphObject in $retObjects)
+ {
+ $params = @{}
+ if($property) { $params.Add("Property", $property) }
+ if($exclude) { $params.Add("ExcludeProperty", $exclude) }
+ foreach($objTmp in ($graphObject | select @params))
+ {
+ $objTmp | Add-Member -NotePropertyName "Object" -NotePropertyValue $graphObject
+ $objects += $objTmp
+ }
+ }
+
+ if($objects.Count -gt 0 -and $SortProperty -and ($objects[0] | GM -MemberType NoteProperty -Name $SortProperty))
+ {
+ $objects = $objects | sort -Property $SortProperty
+ }
+ $objects
+}
+
+function Add-ModuleMenuSections
+{
+
+}
+
+function Get-OrganizationName
+{
+ if(-not $global:Organization)
+ {
+ $global:Organization = (Invoke-GraphRequest "Organization").Value
+ }
+ $global:Organization.displayName
+}
+
+function Set-ObjectPath
+{
+ param($path)
+
+ if(-not $lstMenu.SelectedItem.ObjectPath -or -not $path -or (Test-Path $path) -eq $false) { return }
+
+ Save-RegSetting $lstMenu.SelectedItem.ObjectPath "LastUsedPath" $path
+}
+
+function Get-ObjectPath
+{
+ param($defautValue)
+
+ Get-RegSetting $lstMenu.SelectedItem.ObjectPath "LastUsedPath" $defautValue
+}
+
+function Get-AzureADOrganization
+{
+ param([switch]$short)
+
+ $urlTemp = "/organization"
+
+ if($short) { $urlTemp += "`$select=displayName" }
+
+ Get-GraphObjects $urlTemp
+}
+
+function Get-JsonFileObjects
+{
+ param($path, $Exclude = @("*_settings.json","*_assignments.json"), $SelectedStatus = $true)
+
+ if(-not $path -or (Test-Path $path) -eq $false) { return }
+
+ $params = @{}
+ if($exclude)
+ {
+ $params.Add("Exclude", $exclude)
+ }
+
+ $fileArr = @()
+ foreach($file in (Get-Item -path "$path\*.json" @params))
+ {
+ $obj = New-Object PSObject -Property @{
+ FileName = $file.Name
+ FileInfo = $file
+ Selected = $SelectedStatus
+ Object = (ConvertFrom-Json (Get-Content $file.FullName -Raw))
+ }
+
+ $fileArr += $obj
+ }
+
+ Set-ObjectPath $path
+
+ if(($fileArr | measure).Count -eq 1)
+ {
+ return @($fileArr)
+ }
+ return $fileArr
+}
+
+function Add-DefaultObjectButtons
+{
+ param(
+ [scriptblock]
+ $export,
+ [scriptblock]
+ $import,
+ [scriptblock]
+ $copy
+ )
+
+ if($copy)
+ {
+ $newBtn = New-Object System.Windows.Controls.Button
+ #Copy button
+ $newBtn.Content = 'Copy'
+ $newBtn.Name = 'btnCopy'
+ $newBtn.Margin = "5,0,0,0"
+ $newBtn.Width = "100"
+ $spSubMenu.AddChild($newBtn)
+
+ $newBtn.Add_Click($copy)
+ }
+
+ if($import)
+ {
+ $newBtn = New-Object System.Windows.Controls.Button
+ #Import button
+ $newBtn.Content = 'Import'
+ $newBtn.Name = 'btnImport'
+ $newBtn.Margin = "5,0,0,0"
+ $newBtn.Width = "100"
+ $spSubMenu.AddChild($newBtn)
+
+ $newBtn.Add_Click($import)
+ }
+
+ if($export)
+ {
+ $newBtn = New-Object System.Windows.Controls.Button
+ #Export button
+ $newBtn.Content = 'Export'
+ $newBtn.Name = 'btnExport'
+ $newBtn.Margin = "5,0,0,0"
+ $newBtn.Width = "100"
+ $spSubMenu.AddChild($newBtn)
+
+ $newBtn.Add_Click($export)
+ }
+
+ if($spSubMenu.Children.Count -gt 0)
+ {
+ Show-SubMenu
+ }
+}
+
+########################################################################
+#
+# Export functions
+#
+########################################################################
+
+function Show-DefaultExportGrid
+{
+ param(
+ [ScriptBlock]$ExportAllScript,
+ [ScriptBlock]$ExportSelectedScript,
+ $Extension,
+ $DisplayColumn)
+
+ $exportGrid = [System.Windows.Markup.XamlReader]::Parse(@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $($Extension.Xaml)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"@)
+
+ $btnDoExport = $exportGrid.FindName("btnDoExport")
+ $btnCancel = $exportGrid.FindName("btnCancel")
+ $global:txtExportPath = $exportGrid.FindName("txtExportPath")
+ $btnDoSelectedExport = $exportGrid.FindName("btnDoSelectedExport")
+ $lblSelectedObject = $exportGrid.FindName("lblSelectedObject")
+ $script:btnBrowse = $exportGrid.FindName("browseExportPath")
+ $global:chkAddCompanyName = $exportGrid.FindName("chkAddCompanyName")
+ $global:chkAddObjectType = $exportGrid.FindName("chkAddObjectType")
+ $global:chkExportAssignments = $exportGrid.FindName("chkExportAssignments")
+
+ $btnBrowse.Tag = $global:txtExportPath
+ $btnBrowse.Add_Click({
+ $folder = Get-Folder $global:txtExportPath.Text
+ if($folder) { $this.Tag.Text = $folder }
+ })
+
+ $btnCancel.Add_Click({
+ Set-ObjectGrid
+ })
+
+ if(-not $ExportAllScript)
+ {
+ $ExportAllScript = [ScriptBlock]{
+ Export-AllGraphObjects $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ }
+ }
+
+ if($Extension.Script)
+ {
+ Invoke-Command -ScriptBlock $Extension.Script -ArgumentList $exportGrid
+ }
+
+ $btnDoExport.Add_Click($ExportAllScript)
+
+ if($global:dgObjects.SelectedItem.Object -and $ExportSelectedScript -ne $false)
+ {
+ if(-not $ExportSelectedScript)
+ {
+ $ExportSelectedScript = [ScriptBlock]{
+ Export-SelectedGraphObjects $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ }
+ }
+
+ $btnDoSelectedExport.Add_Click($ExportSelectedScript)
+ if($displayColumn -and $global:dgObjects.SelectedItem."$displayColumn")
+ {
+ $objName = $global:dgObjects.SelectedItem."$displayColumn"
+ }
+ elseif($global:dgObjects.SelectedItem.Object.displayName)
+ {
+ $objName = $global:dgObjects.SelectedItem.Object.displayName
+ }
+ elseif($global:dgObjects.SelectedItem."$($global:dgObjects.Columns[0].Header)")
+ {
+ $objName = $global:dgObjects.SelectedItem."$($global:dgObjects.Columns[0].Header)"
+ }
+
+ $lblSelectedObject.Content = "Selected item: $objName"
+ }
+ else
+ {
+ $btnDoSelectedExport.Visibility = "Collapsed"
+ $lblSelectedObject.Visibility = "Collapsed"
+ }
+
+ Set-ObjectGrid $exportGrid
+}
+
+function Export-AllGraphObjects
+{
+ param($path = "$env:Temp")
+
+ Export-AllGraphObjectsFromSource $path $global:dgObjects.ItemsSource
+}
+
+function Export-AllGraphObjectsFromSource
+{
+ param($path = "$env:Temp", $source, $fileNameProperty = "displayName")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($obj in ($source))
+ {
+ Export-SingleGraphObjects $obj.Object $path $fileNameProperty
+ }
+ }
+}
+
+function Export-SingleGraphObjects
+{
+ param($obj, $path, $fileNameProperty = "displayName")
+
+ if(-not $obj -or -not $path) { return }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($obj."$fileNameProperty")"
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj."$fileNameProperty")).json"
+ ConvertTo-Json $obj -Depth 5| Out-File $fileName -Force
+ $global:exportedObjects++
+ }
+}
+
+function Export-SelectedGraphObjects
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Export-SingleGraphObjects $global:dgObjects.SelectedItem.Object $path
+ }
+}
+
+function Invoke-GraphExportAll
+{
+ param($rootPath)
+
+ if(-not $rootPath) { return }
+
+ $global:exportedObjects = 0
+
+ $rootDir = $rootPath
+
+ $global:runningBulkExport = $true
+
+ if($global:chkAddCompanyName.IsChecked)
+ {
+ $dirObj = Get-AzureADOrganization
+ if($dirObj.Object.displayName)
+ {
+ $rootDir = Join-Path $rootDir $dirObj.Object.displayName
+ }
+ }
+
+ foreach($obj in $global:exportObjects)
+ {
+ if($obj.Selected -ne $true -or -not $obj.Script) { continue }
+
+ Invoke-Command $obj.Script -ArgumentList @($rootDir)
+ }
+
+ $global:runningBulkExport = $false
+}
+
+function Invoke-GraphImportAll
+{
+ param($rootPath)
+
+ if(-not $rootPath) { return }
+
+ $global:importedObjects = 0
+
+ $rootDir = $rootPath
+
+ $global:runningBulkImport = $true
+
+ if($global:chkAddCompanyName.IsChecked)
+ {
+ $dirObj = Get-AzureADOrganization
+ if($dirObj.Object.displayName)
+ {
+ $rootDir = Join-Path $rootDir $dirObj.Object.displayName
+ }
+ }
+
+ foreach($obj in $global:importObjects)
+ {
+ if($obj.Selected -ne $true -or -not $obj.Script) { continue }
+
+ Invoke-Command $obj.Script -ArgumentList @($rootDir)
+ }
+
+ $global:runningBulkImport = $false
+}
+
+
+########################################################################
+#
+# Export functions
+#
+########################################################################
+function Show-DefaultImportGrid
+{
+ param(
+ [ScriptBlock]$ImportAll,
+ [ScriptBlock]$ImportSelected,
+ [ScriptBlock]$GetFiles,
+ $Extension)
+
+ if(-not $script:lastUsedImportFolder)
+ {
+ # Do use root folder each time. Import of single objects are normally under objecy (and company) folder
+ $script:lastUsedImportFolder = Get-SettingValue "IntuneRootFolder"
+ }
+
+ $importGrid = [System.Windows.Markup.XamlReader]::Parse(@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $($Extension.Xaml)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"@)
+
+ $btnDoImport = $importGrid.FindName("btnDoImport")
+ $btnCancel = $importGrid.FindName("btnCancel")
+ $btnGetFiles = $importGrid.FindName("btnGetFiles")
+ $btnImportSelected = $importGrid.FindName("btnImportSelected")
+
+ $global:lstFiles = $importGrid.FindName("lstFiles")
+ $global:grdFilesHeader = $importGrid.FindName("grdFilesHeader")
+ $global:txtImportPath = $importGrid.FindName("txtImportPath")
+ $global:grdObjectsMenu = $importGrid.FindName("grdObjectsMenu")
+ $global:chkCheckAll = $importGrid.FindName("chkCheckAll")
+ $global:chkImportAssignments = $importGrid.FindName("chkImportAssignments")
+
+ $script:btnBrowse = $importGrid.FindName("browsePath")
+ $btnBrowse.Tag = $global:txtImportPath
+ $btnBrowse.Add_Click({
+ $folder = Get-Folder $global:txtImportPath.Text
+ if($folder)
+ {
+ $this.Tag.Text = $folder
+ $script:lastUsedImportFolder = $global:txtImportPath.Text
+ }
+ })
+
+ $btnCancel.Add_Click({
+ if($global:txtImportPath.Text)
+ {
+ $script:lastUsedImportFolder = $global:txtImportPath.Text
+ }
+ Set-ObjectGrid
+ })
+
+ if($Extension.Script)
+ {
+ Invoke-Command -ScriptBlock $Extension.Script -ArgumentList $importGrid
+ }
+
+ $global:chkCheckAll.Add_Click({
+ foreach($obj in $global:lstFiles.Items)
+ {
+ $obj.Selected = $global:chkCheckAll.IsChecked
+ }
+ $global:lstFiles.Items.Refresh()
+ })
+
+ $btnGetFiles.Add_Click({ $script:lastUsedImportFolder = $global:txtImportPath.Text })
+ $btnDoImport.Add_Click({ $script:lastUsedImportFolder = $global:txtImportPath.Text })
+ $btnImportSelected.Add_Click({ $script:lastUsedImportFolder = $global:txtImportPath.Text })
+
+ $btnGetFiles.Add_Click($GetFiles)
+
+ $btnDoImport.Add_Click($ImportAll)
+
+ $btnImportSelected.Add_Click($ImportSelected)
+
+ Set-ObjectGrid $importGrid
+}
+
+function Show-FileListBox
+{
+ $global:grdFilesHeader.Visibility = "Visible"
+ $global:lstFiles.Visibility = "Visible"
+ $global:grdObjectsMenu.Visibility = "Visible"
+}
+
+########################################################################
+#
+# Migration functions
+#
+########################################################################
+
+# Called during export to add migration info for the assignment
+function Add-MigrationInfo
+{
+ param($obj)
+
+ if(-not $obj) { return }
+
+ foreach($objInfo in $obj.target)
+ {
+ if(-not $objInfo."@odata.type") { continue }
+
+ $objType = $objInfo."@odata.type".Trim('#')
+
+ if($objType -eq "microsoft.graph.groupAssignmentTarget" -or
+ $objType -eq "microsoft.graph.exclusionGroupAssignmentTarget")
+ {
+ Add-GroupMigrationObject $objInfo.groupid
+ }
+ elseif($objType -eq "microsoft.graph.allLicensedUsersAssignmentTarget" -or
+ $objType -eq "microsoft.graph.allDevicesAssignmentTarget")
+ {
+ # No need to migrate All Users or All Devices
+ }
+ else
+ {
+ Write-Log "Unsupported migration object: $objType" 3
+ }
+ }
+}
+
+function Add-GroupMigrationObject
+{
+ param($groupId)
+
+ if(-not $groupId) { return }
+
+ $path = $global:txtExportPath.Text
+
+ if($global:chkAddCompanyName.IsChecked)
+ {
+ $path = Join-Path $path $global:organization.displayName
+ }
+
+ # Check if group is already processed
+ if((Get-MigrationObject $groupId)) { return }
+
+ # Get group info
+ $groupObj = Get-AADGroup -groupId $groupId -ErrorAction SilentlyContinue
+ if($groupObj)
+ {
+ # Add group to cache
+ $global:AADObjectCache.Add($groupId, $groupObj)
+
+ # Add group to migration file
+ if((Add-MigrationObject $groupObj $path "Group"))
+ {
+ # Export group info to json file for possible import
+ $grouspPath = Join-Path $path "Groups"
+ if(-not (Test-Path $grouspPath)) { mkdir -Path $grouspPath -Force -ErrorAction SilentlyContinue | Out-Null }
+ $fileName = "$grouspPath\$((Remove-InvalidFileNameChars $groupObj.displayName)).json"
+ ConvertTo-Json $groupObj -Depth 5 | Out-File $fileName -Force
+ }
+ }
+}
+
+function Get-MigrationObject
+{
+ param($objId)
+
+ if(-not $global:AADObjectCache)
+ {
+ $global:AADObjectCache = @{}
+ }
+
+ if($global:AADObjectCache.ContainsKey($objId)) { return $global:AADObjectCache[$objId] }
+}
+
+# Adds an object to migration file if not added previously
+function Add-MigrationObject
+{
+ param($obj, $path, $objType)
+
+ if(-not $objType) { $objType = $obj."@odata.type" }
+
+ $migFileName = Join-Path $path "MigrationTable.json"
+
+ if(-not $global:migFileObj)
+ {
+ if(-not ([IO.File]::Exists($migFileName)))
+ {
+ # Create new file
+ $global:migFileObj = (New-Object PSObject -Property @{
+ TenantId = $global:organization.Id
+ Organization = $global:organization.displayName
+ Objects = @()
+ })
+ }
+ else
+ {
+ # Add to existing file
+ $global:migFileObj = ConvertFrom-Json (Get-Content $migFileName -Raw)
+ }
+ }
+
+ # Make sure Objects property actually exists
+ if(($global:migFileObj | GM -MemberType NoteProperty -Name "Objects") -eq $false)
+ {
+ $global:migFileObj | Add-Member -MemberType NoteProperty -Name "Objects" -Value (@())
+ }
+
+ # Get current object
+ $curObj = $global:migFileObj.Objects | Where Id -eq $obj.Id
+
+ if($curObj) { return $false } # Existing object found so return false to tell that the object was not added
+
+ $global:migFileObj.Objects += (New-Object PSObject -Property @{
+ Id = $obj.Id
+ DisplayName = $obj.displayName
+ Type = $objType
+ })
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+ ConvertTo-Json $global:migFileObj -Depth 10 | Out-File $migFileName -Force
+
+ $true # New object was added
+}
+
+function Get-MigrationObjectsFromFile
+{
+ if($global:MigrationTableCache -or $global:UpdateJsonForMigration -ne $true) { return }
+
+ # Migration table must be located in the root of the import path
+ $path = $global:txtImportPath.Text
+ $found = $false
+ for($i = 0;$i -lt 2;$i++)
+ {
+ if($i -gt 0)
+ {
+ # Get parent directory
+ $path = [io.path]::GetDirectoryName($path)
+ }
+
+ $migFileName = Join-Path $path "MigrationTable.json"
+ try
+ {
+ if([IO.File]::Exists($migFileName))
+ {
+ $found = $true
+ break
+ }
+ }
+ catch {}
+ }
+
+ if($found -eq $false)
+ {
+ return
+ }
+
+ $global:MigrationTableCache = @()
+
+ $migFileObj = ConvertFrom-Json (Get-Content $migFileName -Raw)
+
+ # No need to translate migrated objects in the same environment as exported
+ if($migFileObj.TenantId -eq $global:organization.Id) { return }
+
+ Write-Status "Loading migration objects"
+
+ foreach($migObj in $migFileObj.Objects)
+ {
+ if($migObj.Type -like "*group*")
+ {
+ $obj = Get-AADGroup -Filter "displayName eq '$($migObj.DisplayName)'"
+ if(-not $obj)
+ {
+ # This might not be ok:
+ # The original gour might be synched from on-prem AD. This will create a group with manual assigned membership
+ # ToDo: Add support for goup import from json. This could create dynamic groups
+ Write-Log "Create AAD Group $($migObj.DisplayName)"
+ $obj = New-AADGroup -displayName $($migObj.DisplayName) -mailEnabled $false -mailNickname "NotSet" -securityEnabled $true
+ }
+ $global:MigrationTableCache += (New-Object PSObject -Property @{
+ OriginalId = $migObj.Id
+ Id = $obj.Id
+ })
+ }
+ }
+}
+
+function Update-JsonForEnvironment
+{
+ param($json)
+
+ if($global:UpdateJsonForMigration -ne $true) { return $json }
+
+ # Load file unless previously loaded
+ Get-MigrationObjectsFromFile
+
+ if(-not $global:MigrationTableCache -or $global:MigrationTableCache.Count -eq 0) { return $json }
+
+ # Enumerate all objects in the migration table and replace all exported Id's to Id's in the new environment
+ foreach($migInfo in $global:MigrationTableCache)
+ {
+ $json = $json -replace $migInfo.OriginalId,$migInfo.Id
+ }
+
+ #return updated json
+ $json
+}
+
+########################################################################
+#
+# Assignment functions
+#
+########################################################################
+
+function Import-GraphAssignmentsFile
+{
+ param($obj, $assignmentFile, $assignmentType, $assignmentURL)
+
+ if(-not (Test-Path $assignmentsFile)) { return }
+
+ $assignmentsObj = ConvertFrom-Json (Get-Content $assignmentsFile -Raw)
+ if($assignmentsObj)
+ {
+ Import-GraphAssignments $response $assignmentsObj $assignmentType $assignmentURL
+ }
+
+}
+
+# This uses /assign to create an assignment for an object
+# It will update the json with local information if migration table is used
+function Import-GraphAssignments
+{
+ param($assignments, $assignmentType, $assignmentURL, $assignmentObjectType)
+
+ if(-not $assignments -or -not $assignmentType -or -not $assignmentURL) { return }
+
+ if($global:chkImportAssignments.IsChecked -eq $false) { return }
+
+ $targets = ""
+ $assignments | ForEach-Object {
+ Remove-ObjectProperty $PSItem "Id"
+ if($assignmentObjectType)
+ {
+ $PSItem | Add-Member -MemberType NoteProperty -Name "@odata.type" -Value $assignmentObjectType
+ }
+ $targets += (ConvertTo-Json $PSItem.Target -Depth 5)
+ }
+ $targets = $targets.TrimEnd(',')
+
+$jsonAssignments = $(ConvertTo-Json $assignments -Depth 5)
+if($jsonAssignments.Trim() -notmatch "^[[]|$[]]")
+{
+ $jsonAssignments = "[ $jsonAssignments ]"
+}
+
+$json = @"
+{
+ "$($assignmentType)": $jsonAssignments
+}
+
+"@
+
+ $json = Update-JsonForEnvironment $json
+ $response2 = Invoke-GraphRequest -Url $assignmentURL -Content $json -HttpMethod POST
+}
+
+# This uses /assignments to create an assignment for an object
+# It will update the json with local information if migration table is used
+function Import-GraphAssignments2
+{
+ param($assignments, $assignmentURL)
+
+ if(-not $assignments -or -not $assignmentURL) { return }
+
+ if($global:chkImportAssignments.IsChecked -eq $false) { return }
+
+ $assignments | ForEach-Object {
+ Invoke-GraphRequest -Url $assignmentURL -Content (Update-JsonForEnvironment (ConvertTo-Json $PSItem)) -HttpMethod POST
+ }
+}
+
+function Get-GraphAssignmentsObject
+{
+ param($obj, $fileName)
+
+ $tmpAssignments = $null
+
+ Remove-ObjectProperty $obj "assignments@odata.context"
+
+ if(($obj | GM -MemberType NoteProperty -Name 'assignments'))
+ {
+ $tmpAssignments = $obj.assignments
+ $obj.PSObject.Properties.Remove('assignments')
+ }
+
+ if(-not $tmpAssignments -and $fileName -and (Test-Path $fileName))
+ {
+ $tmpAssignments = ConvertFrom-Json (Get-Content $fileName -Raw)
+ }
+
+ $tmpAssignments
+}
\ No newline at end of file
diff --git a/Extensions/PowerShellScripts.psm1 b/Extensions/PowerShellScripts.psm1
new file mode 100644
index 0000000..8b04e70
--- /dev/null
+++ b/Extensions/PowerShellScripts.psm1
@@ -0,0 +1,306 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-PowerShellScriptName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-PowerShellScripts}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-PowerShellScriptName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all powershell scripts"
+ Import-AllPowerShellScriptObjects (Join-Path $rootFolder (Get-PowerShellScriptFolderName))
+ }
+ })
+}
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-PowerShellScriptName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+ Write-Status "Export all powershell scripts"
+ Get-PowerShellScriptObjects | ForEach-Object { Export-SinglePowerShellScript $PSItem.Object (Join-Path $rootFolder (Get-PowerShellScriptFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-PowerShellScriptFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-PowerShellScriptName
+{
+ return "PowerShell Script"
+}
+
+function Get-PowerShellScriptFolderName
+{
+ return "PowerShell"
+}
+
+function Get-PowerShellScripts
+{
+ Write-Status "Loading PowerShell objects"
+ $dgObjects.ItemsSource = @(Get-PowerShellScriptObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllPowerShellScripts $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedPowerShellScript $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllPowerShellScriptObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-PowerShellScriptObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ $exportExtension = (New-Object PSObject -Property @{
+ Xaml = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"@
+ Script = [ScriptBlock]{
+ param($form)
+ $script:chkExportScript = $form.FindName("chkExportScript")
+ }
+ })
+
+ $script:exportParams.Add("Extension", $exportExtension)
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles}) -copy ([scriptblock]{Copy-PowerShellScript})
+
+ #Add download button
+ $btnDownload = New-Object System.Windows.Controls.Button
+ $btnDownload.Content = 'Download'
+ $btnDownload.Name = 'btnDownload'
+ $btnDownload.Margin = "5,0,0,0"
+ $btnDownload.Width = "100"
+ $spSubMenu.Children.Insert(0, $btnDownload)
+
+ $btnDownload.Add_Click({
+ Invoke-DownloadScript
+ })
+}
+
+function Get-PowerShellScriptObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/deviceManagementScripts"
+}
+
+function Export-AllPowerShellScripts
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SinglePowerShellScript $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedPowerShellScript
+{
+ param($path = "$env:Temp")
+
+ Export-SinglePowerShellScript $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SinglePowerShellScript
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-PowerShellScriptFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+ $obj = Invoke-GraphRequest -Url "/deviceManagement/deviceManagementScripts/$($psObj.id)?`$expand=assignments"
+ if($obj)
+ {
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+ if($script:chkExportScript.IsChecked)
+ {
+ $fileName = "$path\$($obj.FileName)"
+ [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($obj.scriptContent)) | Out-File $fileName -Force
+ }
+ Add-MigrationInfo $obj.assignments
+ $global:exportedObjects++
+ }
+ }
+}
+
+function Copy-PowerShellScript
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect PowerShell script you want to copy", "Error", "OK", "Error")
+ return
+ }
+
+ $ret = Show-InputDialog "Copy PowerShell script" "Select name for the new object" "$($global:dgObjects.SelectedItem.displayName) - Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ $obj = Invoke-GraphRequest -Url "/deviceManagement/deviceManagementScripts/$($dgObjects.SelectedItem.id)"
+ if($obj)
+ {
+ # Import new profile
+ $obj.displayName = $ret
+ Import-PowerShellScript $obj | Out-null
+
+ $dgObjects.ItemsSource = @(Get-PowerShellScriptObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+function Import-PowerShellScript
+{
+ param($obj)
+
+ Remove-ObjectProperty $obj "id"
+ Remove-ObjectProperty $obj "createdDateTime"
+ Remove-ObjectProperty $obj "lastModifiedDateTime"
+ Remove-ObjectProperty $obj "assignments@odata.context"
+ Remove-ObjectProperty $obj "assignments"
+
+ Write-Status "Import $($obj.displayName)"
+
+ Invoke-GraphRequest -Url "/deviceManagement/deviceManagementScripts" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST
+
+}
+
+function Import-AllPowerShellScriptObjects
+{
+ param(
+ $path = "$env:Temp"
+ )
+
+ Import-PowerShellScriptObjects (Get-JsonFileObjects $path)
+}
+
+function Import-PowerShellScriptObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import PowerShell scripts"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import PowerShell script: $($obj.Object.displayName)"
+
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+
+ $response = Import-PowerShellScript $obj.Object
+
+ if($response)
+ {
+ $global:importedObjects++
+ Import-GraphAssignments $assignments "deviceManagementScriptAssignments" "/deviceManagement/deviceManagementScripts/$($response.Id)/assign"
+ }
+ }
+
+ $dgObjects.ItemsSource = @(Get-PowerShellScriptObjects)
+ Write-Status ""
+}
+
+function Invoke-DownloadScript
+{
+ if(-not $global:dgObjects.SelectedItem.Object.id) { return }
+ $obj = Invoke-GraphRequest -Url "/deviceManagement/deviceManagementScripts/$($global:dgObjects.SelectedItem.Object.id)"
+ if($obj.scriptContent)
+ {
+ Write-Log "Download PowerShell script '$($obj.FileName)' from $($obj.displayName)"
+ $fileName = "$path\$($obj.FileName)"
+
+ $dlgSave = New-Object -Typename System.Windows.Forms.SaveFileDialog
+ $dlgSave.InitialDirectory = Get-SettingValue "IntuneRootFolder" $env:Temp
+ $dlgSave.FileName = $obj.FileName
+ if($dlgSave.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK -and $dlgSave.Filename)
+ {
+ [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($obj.scriptContent)) | Out-File $dlgSave.Filename -Force
+ }
+ }
+}
\ No newline at end of file
diff --git a/Extensions/TermsAndConditions.psm1 b/Extensions/TermsAndConditions.psm1
new file mode 100644
index 0000000..d4caa47
--- /dev/null
+++ b/Extensions/TermsAndConditions.psm1
@@ -0,0 +1,243 @@
+########################################################
+#
+# Common module functions
+#
+########################################################
+function Add-ModuleMenuItems
+{
+ Add-MenuItem (New-Object PSObject -Property @{
+ Title = (Get-TermsAndConditionName)
+ MenuID = "IntuneGraphAPI"
+ Script = [ScriptBlock]{Get-TermsAndConditions}
+ })
+}
+
+function Get-SupportedImportObjects
+{
+ $global:importObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-TermsAndConditionName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Import all terms and conditions"
+ Import-AllTermsAndConditionObjects (Join-Path $rootFolder (Get-TermsAndConditionFolderName))
+ }
+ })
+}
+
+
+function Get-SupportedExportObjects
+{
+ $global:exportObjects += (New-Object PSObject -Property @{
+ Selected = $true
+ Title = (Get-TermsAndConditionName)
+ Script = [ScriptBlock]{
+ param($rootFolder)
+
+ Write-Status "Export all Intune terms and conditions"
+ Get-TermsAndConditionObjects | ForEach-Object { Export-SingleTermsAndCondition $PSItem.Object (Join-Path $rootFolder (Get-TermsAndConditionFolderName)) }
+ }
+ })
+}
+
+function Export-AllObjects
+{
+ param($addObjectSubfolder)
+
+ $subFolder = ""
+ if($addObjectSubfolder) { $subFolder = Get-TermsAndConditionFolderName }
+}
+
+########################################################
+#
+# Object specific functions
+#
+########################################################
+function Get-TermsAndConditionName
+{
+ return "Terms and Conditions"
+}
+
+function Get-TermsAndConditionFolderName
+{
+ return "TermsAndConditions"
+}
+
+function Get-TermsAndConditions
+{
+ Write-Status "Loading terms and conditions"
+ $dgObjects.ItemsSource = @(Get-TermsAndConditionObjects)
+
+ #Scriptblocks that will perform the export tasks. empty by default
+ $script:exportParams = @{}
+ $script:exportParams.Add("ExportAllScript", [ScriptBlock]{
+ Export-AllTermsAndConditions $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{
+ Export-SelectedTermsAndCondition $global:txtExportPath.Text
+ Set-ObjectGrid
+ Write-Status ""
+ })
+
+ #Scriptblock that will perform the import all files
+ $script:importAll = [ScriptBlock]{
+ Import-AllTermsAndConditionObjects $global:txtImportPath.Text
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will perform the import of selected files
+ $script:importSelected = [ScriptBlock]{
+ Import-TermsAndConditionObjects $global:lstFiles.ItemsSource -Selected
+ Set-ObjectGrid
+ }
+
+ #Scriptblock that will read json files
+ $script:getImportFiles = [ScriptBlock]{
+ Show-FileListBox
+ $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json")
+ }
+
+ Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles}) -copy ([scriptblock]{Copy-TermsAndCondition})
+}
+
+function Get-TermsAndConditionObjects
+{
+ Get-GraphObjects -Url "/deviceManagement/termsAndConditions"
+}
+
+function Export-AllTermsAndConditions
+{
+ param($path = "$env:Temp")
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ foreach($objTmp in ($global:dgObjects.ItemsSource))
+ {
+ Export-SingleTermsAndCondition $objTmp.Object $path
+ }
+ }
+}
+
+function Export-SelectedTermsAndCondition
+{
+ param($path = "$env:Temp")
+
+ Export-SingleTermsAndCondition $global:dgObjects.SelectedItem.Object $path
+}
+
+function Export-SingleTermsAndCondition
+{
+ param($psObj, $path = "$env:Temp")
+
+ if(-not $psObj) { return }
+
+ if($global:runningBulkExport -ne $true)
+ {
+ if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName }
+ if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-TermsAndConditionFolderName) }
+ }
+
+ if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null }
+
+ if(Test-Path $path)
+ {
+ Write-Status "Export $($psObj.displayName)"
+ $obj = Invoke-GraphRequest -Url "/deviceManagement/termsAndConditions/$($psObj.id)" #?`$expand=assignments"
+ if($obj)
+ {
+ # ?`$expand=assignments is not working so get assignments
+ $assignments = Invoke-GraphRequest -Url "/deviceManagement/termsAndConditions/$($obj.id)/assignments"
+ if($assignments.value)
+ {
+ $obj | Add-Member -NotePropertyName "assignments" -NotePropertyValue $assignments.value
+ }
+
+ $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json"
+ ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force
+
+ Add-MigrationInfo $obj.assignments
+ }
+ $global:exportedObjects++
+ }
+}
+
+function Copy-TermsAndCondition
+{
+ if(-not $dgObjects.SelectedItem)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect terms and conditions item you want to copy", "Error", "OK", "Error")
+ return
+ }
+
+ $ret = Show-InputDialog "Copy terms and conditions" "Select name for the new object" "$($dgObjects.SelectedItem.displayName) - Copy"
+
+ if($ret)
+ {
+ # Export profile
+ Write-Status "Export $($dgObjects.SelectedItem.displayName)"
+ # Get full object for export
+ $obj = Invoke-GraphRequest -Url "/deviceManagement/termsAndConditions/$($dgObjects.SelectedItem.Object.id)"
+ if($obj)
+ {
+ # Import new profile
+ $obj.displayName = $ret
+ Import-TermsAndCondition $obj | Out-Null
+
+ $dgObjects.ItemsSource = @(Get-TermsAndConditionObjects)
+ }
+ Write-Status ""
+ }
+ $dgObjects.Focus()
+}
+
+function Import-TermsAndCondition
+{
+ param($obj)
+
+ Write-Status "Import $($obj.displayName)"
+
+ Invoke-GraphRequest -Url "/deviceManagement/termsAndConditions" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST
+}
+
+function Import-AllTermsAndConditionObjects
+{
+ param($path = "$env:Temp")
+
+ Import-TermsAndConditionObjects (Get-JsonFileObjects $path)
+}
+
+function Import-TermsAndConditionObjects
+{
+ param(
+ $Objects,
+
+ [switch]
+ $Selected
+ )
+
+ Write-Status "Import terms and conditions"
+
+ foreach($obj in $objects)
+ {
+ if($Selected -and $obj.Selected -ne $true) { continue }
+
+ Write-Log "Import Terms and Conditions: $($obj.Object.displayName)"
+
+ $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json")
+
+ $response = Import-TermsAndCondition $obj.Object
+ if($response)
+ {
+ $global:importedObjects++
+ Import-GraphAssignments2 $assignments "/deviceManagement/termsAndConditions/$($response.Id)/assignments"
+ }
+ }
+ $dgObjects.ItemsSource = @(Get-TermsAndConditionObjects)
+ Write-Status ""
+}
\ No newline at end of file