3.3.0
Beta release
This commit is contained in:
Binary file not shown.
@@ -69,7 +69,11 @@ function Initialize-CloudAPIManagement
|
||||
[string]
|
||||
$View = "",
|
||||
[switch]
|
||||
$ShowConsoleWindow
|
||||
$ShowConsoleWindow,
|
||||
[switch]
|
||||
$JSonSettings,
|
||||
[string]
|
||||
$JSonFile
|
||||
)
|
||||
|
||||
$global:wpfNS = "xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'"
|
||||
@@ -99,6 +103,16 @@ function Initialize-CloudAPIManagement
|
||||
Hide-Console
|
||||
}
|
||||
|
||||
if($JSonSettings -eq $true)
|
||||
{
|
||||
$global:UseJSonSettings = $true
|
||||
$global:JSonSettingFile = $JSonFile
|
||||
}
|
||||
else
|
||||
{
|
||||
$global:UseJSonSettings = $false
|
||||
}
|
||||
|
||||
$global:txtSplashText.Text = "Unblock files"
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
Unblock-AllFiles $PSScriptRoot
|
||||
|
||||
330
Core.psm1
330
Core.psm1
@@ -6,13 +6,12 @@ Core UI and Settings fatures for the CloudAPIPowerShellManager solution
|
||||
This module handles the WPF UI
|
||||
|
||||
.NOTES
|
||||
Version: 3.1.0
|
||||
Author: Mikael Karlsson
|
||||
#>
|
||||
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'3.1.7'
|
||||
'3.3.0'
|
||||
}
|
||||
|
||||
function Start-CoreApp
|
||||
@@ -35,10 +34,18 @@ function Start-CoreApp
|
||||
# Load all modules in the Modules folder
|
||||
$global:modulesPath = [IO.Path]::GetDirectoryName($PSCommandPath) + "\Extensions"
|
||||
|
||||
#Import-Module ($PSScriptRoot + "\Core.psm1") -Force -Global
|
||||
|
||||
Add-DefaultSettings
|
||||
|
||||
if($global:UseJSonSettings -eq $true)
|
||||
{
|
||||
Initialize-JsonSettings
|
||||
}
|
||||
|
||||
if($global:UseJSonSettings -eq $false)
|
||||
{
|
||||
Write-Log "Use settings in registry"
|
||||
}
|
||||
|
||||
Write-Log "#####################################################################################"
|
||||
Write-Log "Application started"
|
||||
Write-Log "#####################################################################################"
|
||||
@@ -53,7 +60,7 @@ function Start-CoreApp
|
||||
exit 1
|
||||
}
|
||||
|
||||
$global:Debug = Get-SettingValue "Debug"
|
||||
Initialize-Settings
|
||||
$global:currentViewObject = $null
|
||||
$global:FirstTimeRunning = ((Get-Setting "" "FirstTimeRunning" "true") -eq "true")
|
||||
$global:MainAppStarted = $false
|
||||
@@ -795,34 +802,230 @@ function Expand-FileName
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reg functions
|
||||
#region Save/Read Settings functions
|
||||
########################################################################
|
||||
#
|
||||
# Reg functions
|
||||
# Save/Read Settings
|
||||
#
|
||||
########################################################################
|
||||
function Initialize-Settings
|
||||
{
|
||||
param([switch]$Updated)
|
||||
|
||||
$global:Debug = Get-SettingValue "Debug"
|
||||
$global:logFile = $null
|
||||
$global:logFileMaxSize = $null
|
||||
|
||||
if($Updated -eq $true)
|
||||
{
|
||||
Invoke-ModuleFunction "Invoke-SettingsUpdated"
|
||||
}
|
||||
}
|
||||
|
||||
function Initialize-JsonSettings
|
||||
{
|
||||
if(-not $global:JSonSettingFile)
|
||||
{
|
||||
$global:JSonSettingFile = "$($env:LOCALAPPDATA)\CloudAPIPowerShellManagement\Settings.json"
|
||||
$fi = [IO.FileInfo]$global:JSonSettingFile
|
||||
if($fi.Exists -eq $false)
|
||||
{
|
||||
Export-Settings $fi.FullName
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$fi = [IO.FileInfo]$global:JSonSettingFile
|
||||
if($fi.Exists -eq $false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Write-Host "Settings file $($fi.FullName) does not exist. Create empty settings"
|
||||
@{} | ConvertTo-Json | Out-File -FilePath $global:JSonSettingFile -Force -Encoding utf8
|
||||
}
|
||||
catch
|
||||
{
|
||||
Clear-JsonSettingsValues
|
||||
Write-LogError "Failed to create json setting file $($fi.FullName). Veirfy write access. Registry settings will be used." $_.Exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fi = [IO.FileInfo]$global:JSonSettingFile
|
||||
if($fi.Exists -eq $true)
|
||||
{
|
||||
try
|
||||
{
|
||||
$global:JsonSettingsObj = (ConvertFrom-Json (Get-Content -Path $fi.FullName -Raw))
|
||||
Write-Log "Use json settings file: $($fi.FullName)"
|
||||
return
|
||||
}
|
||||
catch
|
||||
{
|
||||
Clear-JsonSettingsValues
|
||||
Write-LogError "Failed to read json setting file $($fi.FullName). Registry settings will be used." $_.Exception
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear-JsonSettingsValues
|
||||
Write-LogError "Could not find json setting file $($fi.FullName). Registry settings will be used"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function Clear-JsonSettingsValues
|
||||
{
|
||||
# Failed - Revert back to reg settings
|
||||
$global:JsonSettingsObj = $null
|
||||
$global:JSonSettingFile = $null
|
||||
$global:UseJSonSettings = $false
|
||||
}
|
||||
|
||||
function Save-Setting
|
||||
{
|
||||
param($SubPath, $Key, $Value, $Type = "String")
|
||||
param($SubPath = "", $Key = "", $Value, $Type = "String")
|
||||
|
||||
$regPath = Get-RegPath $SubPath
|
||||
if((Test-Path $regPath) -eq $false)
|
||||
if($global:JsonSettingsObj -and $global:JSonSettingFile)
|
||||
{
|
||||
New-Item (Get-RegPath $SubPath) -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
if($SubPath)
|
||||
{
|
||||
$arrParts = $SubPath.Split(@('/','\'))
|
||||
}
|
||||
else
|
||||
{
|
||||
$arrParts = @()
|
||||
}
|
||||
|
||||
$parentSetting = $global:JsonSettingsObj
|
||||
|
||||
foreach($part in $arrParts)
|
||||
{
|
||||
if(($parentSetting.PSObject.Properties | Where Name -eq $part))
|
||||
{
|
||||
$parentSetting = $parentSetting.$part
|
||||
}
|
||||
else
|
||||
{
|
||||
$parentSetting.$part = @()
|
||||
$parentSetting = $parentSetting.$part
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if($null -eq $Value)
|
||||
{
|
||||
if(($parentSetting.PSObject.Properties | Where Name -eq $Key))
|
||||
{
|
||||
$parentSetting.PSObject.Properties.Remove($Key) | Out-Null
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if($Type -eq "String" -and $null -ne $value)
|
||||
{
|
||||
$Value = $value.ToString()
|
||||
}
|
||||
elseif($Type -eq "DWord" -and $null -ne $Value)
|
||||
{
|
||||
$Value = [Int]::Parse($Value)
|
||||
}
|
||||
|
||||
if(-not ($parentSetting.PSObject.Properties | Where Name -eq $Key))
|
||||
{
|
||||
$parentSetting | Add-Member -MemberType NoteProperty -Name $Key -Value $Value
|
||||
}
|
||||
else
|
||||
{
|
||||
$parentSetting.$Key = $Value
|
||||
}
|
||||
}
|
||||
|
||||
$global:JsonSettingsObj | ConvertTo-Json -Depth 20 | Out-File -LiteralPath $global:JSonSettingFile -Force -Encoding utf8
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to save json setting value $Key" $_.Exception
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$regPath = Get-RegPath $SubPath
|
||||
if((Test-Path $regPath) -eq $false)
|
||||
{
|
||||
New-Item (Get-RegPath $SubPath) -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
|
||||
New-ItemProperty -Path $regPath -Name $Key -Value $Value -Type $Type -Force | Out-Null
|
||||
}
|
||||
New-ItemProperty -Path $regPath -Name $Key -Value $Value -Type $Type -Force | Out-Null
|
||||
}
|
||||
|
||||
function Get-Setting
|
||||
{
|
||||
param($SubPath, $Key, $defautValue)
|
||||
param($SubPath = "", $Key = "", $defautValue)
|
||||
|
||||
try
|
||||
{
|
||||
$val = Get-ItemPropertyValue -Path (Get-RegPath $SubPath) -Name $Key -ErrorAction SilentlyContinue
|
||||
if(-not $key)
|
||||
{
|
||||
return
|
||||
}
|
||||
catch { }
|
||||
|
||||
$val = $null
|
||||
|
||||
if($global:JsonSettingsObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if($SubPath)
|
||||
{
|
||||
$arrParts = $SubPath.Split(@('/','\'))
|
||||
}
|
||||
else
|
||||
{
|
||||
$arrParts = @()
|
||||
}
|
||||
|
||||
$parentSetting = $global:JsonSettingsObj
|
||||
$found = $true
|
||||
|
||||
foreach($part in $arrParts)
|
||||
{
|
||||
if(($parentSetting.PSObject.Properties | Where Name -eq $part))
|
||||
{
|
||||
$parentSetting = $parentSetting.$part
|
||||
}
|
||||
else
|
||||
{
|
||||
$found = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if($null -ne $parentSetting.$Key -and $found)
|
||||
{
|
||||
$val = $parentSetting.$Key
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to read json setting value $Key" $_.Exception
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
$val = Get-ItemPropertyValue -Path (Get-RegPath $SubPath) -Name $Key -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch
|
||||
{
|
||||
if($_.Exception.HResult -ne -2147024809) # Skip reporting missing values
|
||||
{
|
||||
Write-LogError "Failed to read registry setting value $Key" $_.Exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(-not $val)
|
||||
{
|
||||
$defautValue
|
||||
@@ -845,6 +1048,77 @@ function Get-RegPath
|
||||
|
||||
$path
|
||||
}
|
||||
|
||||
function Export-Settings
|
||||
{
|
||||
param($fileName)
|
||||
|
||||
try
|
||||
{
|
||||
$fi = [IO.FileInfo]$fileName
|
||||
if($fi.Directory.Exists -eq $false)
|
||||
{
|
||||
$fi.Directory.Create()
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to create folder for settings file" $_.Exception
|
||||
return
|
||||
}
|
||||
|
||||
$settingObj = [ordered]@{}
|
||||
Add-RegKeyToSettings $settingObj "HKCU:\Software\CloudAPIPowerShellManagement"
|
||||
$json = $settingObj | ConvertTo-Json -Depth 20
|
||||
try
|
||||
{
|
||||
$json | Out-File -filePath $fileName -encoding utf8 -Force -ErrorAction Stop
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to save json setting file" $_.Exception
|
||||
}
|
||||
}
|
||||
|
||||
function Add-RegKeyToSettings
|
||||
{
|
||||
param($settingObj, $regKey)
|
||||
|
||||
try
|
||||
{
|
||||
$keyObj = Get-Item -Path $regKey
|
||||
foreach($keyValue in ($keyObj.GetValueNames() | Sort))
|
||||
{
|
||||
try
|
||||
{
|
||||
$settingObj.Add($keyValue, $keyObj.GetValue($keyValue))
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to add setting from reg key $keyValue in $regKey" $_.Exception
|
||||
}
|
||||
}
|
||||
|
||||
foreach($subKey in ($keyObj.GetSubKeyNames() | Sort))
|
||||
{
|
||||
|
||||
$settingObjSub = [ordered]@{}
|
||||
$settingObj.Add($subKey, $settingObjSub)
|
||||
try
|
||||
{
|
||||
Add-RegKeyToSettings $settingObjSub ($regKey + '\' + $subKey)
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to add setting for reg subkey $subKey in $regKey" $_.Exception
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to add reg keys to json settings" $_.Exception
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Setting functions
|
||||
@@ -1028,13 +1302,30 @@ function Show-SettingsForm
|
||||
|
||||
Add-XamlEvent $settingsForm "btnSave" "Add_Click" ({
|
||||
Save-AllSettings
|
||||
$global:Debug = Get-SettingValue "Debug"
|
||||
})
|
||||
|
||||
Add-XamlEvent $settingsForm "btnClose" "Add_Click" ({
|
||||
Show-ModalObject
|
||||
})
|
||||
|
||||
if($JsonSettingsObj)
|
||||
{
|
||||
Set-XamlProperty $settingsForm "btnExport" "Visibility" "Collapsed"
|
||||
}
|
||||
else
|
||||
{
|
||||
Add-XamlEvent $settingsForm "btnExport" "Add_Click" ({
|
||||
$sf = [System.Windows.Forms.SaveFileDialog]::new()
|
||||
$sf.FileName = $script:currentObjName
|
||||
$sf.DefaultExt = "*.json"
|
||||
$sf.Filter = "Json (*.json)|*.json|All files (*.*)|*.*"
|
||||
if($sf.ShowDialog() -eq "OK")
|
||||
{
|
||||
Export-Settings $sf.FileName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$tmp = $global:appSettingSections | Where-Object Id -eq "General"
|
||||
if($tmp.Values.Count -gt 0)
|
||||
{
|
||||
@@ -1174,6 +1465,9 @@ function Save-AllSettings
|
||||
{
|
||||
& $global:currentViewObject.ViewInfo.SaveSettings
|
||||
}
|
||||
|
||||
Initialize-Settings -Updated
|
||||
|
||||
Start-Sleep -Seconds 1 # It goes to quick...ToDo: Do this in a better way
|
||||
Write-Status ""
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ Objects can be compared based on Properties or Documentatation info.
|
||||
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'1.0.7'
|
||||
'1.0.8'
|
||||
}
|
||||
|
||||
function Invoke-InitializeModule
|
||||
@@ -50,16 +50,16 @@ function Add-CompareProvider
|
||||
if($global:compareProviders.Count -eq 0)
|
||||
{
|
||||
$global:compareProviders += [PSCustomObject]@{
|
||||
Name = "Exported File"
|
||||
Name = "Intune Objects with Exported Files"
|
||||
Value = "export"
|
||||
ObjectCompare = { Compare-ObjectsBasedonProperty @args }
|
||||
BulkCompare = { Start-BulkCompareExportObjects @args }
|
||||
ProviderOptions = "CompareExportOptions"
|
||||
Activate = { Invoke-ActivateCompareExportObjects @args }
|
||||
Activate = { Invoke-ActivateCompareWithExportObjects @args }
|
||||
}
|
||||
|
||||
$global:compareProviders += [PSCustomObject]@{
|
||||
Name = "Named Objects"
|
||||
Name = "Named Objects in Intune"
|
||||
Value = "name"
|
||||
BulkCompare = { Start-BulkCompareNamedObjects @args }
|
||||
ProviderOptions = "CompareNamedOptions"
|
||||
@@ -67,6 +67,15 @@ function Add-CompareProvider
|
||||
RemoveProperties = @("Id")
|
||||
}
|
||||
|
||||
$global:compareProviders += [PSCustomObject]@{
|
||||
Name = "Files in Exported Folders"
|
||||
Value = "exportedFolders"
|
||||
ObjectCompare = { Compare-ObjectsBasedonProperty @args }
|
||||
BulkCompare = { Start-BulkCompareExportFolders @args }
|
||||
ProviderOptions = "CompareExportedFilesOptions"
|
||||
Activate = { Invoke-ActivateCompareExportedObjects @args }
|
||||
}
|
||||
|
||||
$global:compareProviders += [PSCustomObject]@{
|
||||
Name = "Existing objects"
|
||||
Value = "existing"
|
||||
@@ -269,7 +278,9 @@ function Set-CompareProviderOptions
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-ActivateCompareExportObjects
|
||||
# Compare Intune object with exported folder
|
||||
|
||||
function Invoke-ActivateCompareWithExportObjects
|
||||
{
|
||||
param($providerOptions, $firstTime)
|
||||
|
||||
@@ -292,6 +303,39 @@ function Invoke-ActivateCompareExportObjects
|
||||
}
|
||||
}
|
||||
|
||||
# Compare two exported folders
|
||||
function Invoke-ActivateCompareExportedObjects
|
||||
{
|
||||
param($providerOptions, $firstTime)
|
||||
|
||||
if($firstTime)
|
||||
{
|
||||
$path = Get-Setting "" "LastUsedFullPath"
|
||||
if($path)
|
||||
{
|
||||
$path = [IO.Directory]::GetParent($path).FullName
|
||||
}
|
||||
Set-XamlProperty $providerOptions "txtExportPathSource" "Text" (?? $path (Get-SettingValue "RootFolder"))
|
||||
Set-XamlProperty $providerOptions "txtExportPathCompare" "Text" (Get-SettingValue "ExportPathCompare")
|
||||
|
||||
Add-XamlEvent $providerOptions "browseExportPathSource" "add_click" ({
|
||||
$folder = Get-Folder (Get-XamlProperty $this.Parent "txtExportPathSource" "Text") "Select root folder for source"
|
||||
if($folder)
|
||||
{
|
||||
Set-XamlProperty $this.Parent "txtExportPathSource" "Text" $folder
|
||||
}
|
||||
})
|
||||
|
||||
Add-XamlEvent $providerOptions "browseExportPathCompare" "add_click" ({
|
||||
$folder = Get-Folder (Get-XamlProperty $this.Parent "txtExportPathCompare" "Text") "Select folder to compare the source with"
|
||||
if($folder)
|
||||
{
|
||||
Set-XamlProperty $this.Parent "txtExportPathCompare" "Text" $folder
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-ActivateCompareNamesObjects
|
||||
{
|
||||
param($providerOptions, $firstTime)
|
||||
@@ -468,6 +512,8 @@ function Start-BulkCompareExportObjects
|
||||
Write-Log "Start bulk Exported Objects compare"
|
||||
Write-Log "****************************************************************"
|
||||
$compareObjectsResult = @()
|
||||
|
||||
$txtNameFilter = (Get-XamlProperty $global:ccContentProviderOptions.Content "txtCompareNameFilter" "Text").Trim()
|
||||
$rootFolder = (Get-XamlProperty $global:ccContentProviderOptions.Content "txtExportPath" "Text")
|
||||
|
||||
$compareProps = $script:defaultCompareProps
|
||||
@@ -521,12 +567,20 @@ function Start-BulkCompareExportObjects
|
||||
Write-Log "Object from file '$($fileObj.FullName)' has no Id property. Compare not supported" 2
|
||||
continue
|
||||
}
|
||||
|
||||
$objName = Get-GraphObjectName $fileObj.Object $fileObj.ObjectType
|
||||
|
||||
if($txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
|
||||
{
|
||||
continue
|
||||
}
|
||||
|
||||
$curObject = $graphObjects | Where { $_.Object.Id -eq $fileObj.Object.Id }
|
||||
|
||||
if(-not $curObject)
|
||||
{
|
||||
# Add objects that are exported but deleted
|
||||
Write-Log "Object '$((Get-GraphObjectName $fileObj.Object $fileObj.ObjectType))' with id $($fileObj.Object.Id) not found in Intune. Deleted?" 2
|
||||
Write-Log "Object '$($objName)' with id $($fileObj.Object.Id) not found in Intune. Deleted?" 2
|
||||
$compareProperties = @([PSCustomObject]@{
|
||||
Object1Value = $null
|
||||
Object2Value = (Get-GraphObjectName $fileObj.Object $item.ObjectType)
|
||||
@@ -554,13 +608,19 @@ function Start-BulkCompareExportObjects
|
||||
# Add objects that are not exported
|
||||
if(($compareObjectsResult | Where { $_.Id -eq $graphObj.Id})) { continue }
|
||||
|
||||
$objName = Get-GraphObjectName $graphObj.Object $item.ObjectType
|
||||
if($txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
|
||||
{
|
||||
continue
|
||||
}
|
||||
|
||||
$compareObjectsResult += [PSCustomObject]@{
|
||||
Object1 = $curObject.Object
|
||||
Object2 = $null
|
||||
ObjectType = $item.ObjectType
|
||||
Id = $graphObj.Id
|
||||
Result = @([PSCustomObject]@{
|
||||
Object1Value = (Get-GraphObjectName $graphObj.Object $item.ObjectType)
|
||||
Object1Value = $objName
|
||||
Object2Value = $null
|
||||
Match = $false
|
||||
})
|
||||
@@ -594,18 +654,18 @@ function Start-BulkCompareExportObjects
|
||||
|
||||
if($outputType -eq "objectType")
|
||||
{
|
||||
Save-BulkCompareResults $compResultValues (Join-Path $rootFolder "Compare_$(((Get-Date).ToString("yyyyMMdd-HHmm"))).csv") $compareProps
|
||||
Save-BulkCompareResults $compResultValues (Join-Path $folder "Compare_$(((Get-Date).ToString("yyyyMMdd-HHmm"))).csv") $compareProps
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Log "Folder $folder not found. Skipping import" 2
|
||||
Write-Log "Folder $folder not found. Skipping compare" 2
|
||||
}
|
||||
}
|
||||
|
||||
if($outputType -eq "all" -and $compResultValues.Count -gt 0)
|
||||
{
|
||||
Save-BulkCompareResults $compResultValues (Join-Path $folder "Compare_$(((Get-Date).ToString("yyyyMMDD-HHmm"))).csv") $compareProps
|
||||
Save-BulkCompareResults $compResultValues (Join-Path $rootFolder "Compare_$(((Get-Date).ToString("yyyyMMDD-HHmm"))).csv") $compareProps
|
||||
}
|
||||
|
||||
Write-Log "****************************************************************"
|
||||
@@ -618,6 +678,185 @@ function Start-BulkCompareExportObjects
|
||||
}
|
||||
}
|
||||
|
||||
function Start-BulkCompareExportFolders
|
||||
{
|
||||
Write-Log "****************************************************************"
|
||||
Write-Log "Start bulk Exported Folders compare"
|
||||
Write-Log "****************************************************************"
|
||||
$compareObjectsResult = @()
|
||||
|
||||
$txtNameFilter = (Get-XamlProperty $global:ccContentProviderOptions.Content "txtCompareNameFilter" "Text").Trim()
|
||||
$rootFolderSource = (Get-XamlProperty $global:ccContentProviderOptions.Content "txtExportPathSource" "Text")
|
||||
$rootFolderCompare = (Get-XamlProperty $global:ccContentProviderOptions.Content "txtExportPathCompare" "Text")
|
||||
|
||||
$compareProps = $script:defaultCompareProps
|
||||
|
||||
foreach($removeProp in $global:cbCompareProvider.SelectedItem.RemoveProperties)
|
||||
{
|
||||
$compareProps.Remove($removeProp) | Out-Null
|
||||
}
|
||||
|
||||
foreach($removeProp in $global:cbCompareType.SelectedItem.RemoveProperties)
|
||||
{
|
||||
$compareProps.Remove($removeProp) | Out-Null
|
||||
}
|
||||
|
||||
if(-not $rootFolderSource -or -not $rootFolderCompare)
|
||||
{
|
||||
[System.Windows.MessageBox]::Show("Both folders must be specified", "Error", "OK", "Error")
|
||||
return
|
||||
}
|
||||
|
||||
if([IO.Directory]::Exists($rootFolderSource) -eq $false)
|
||||
{
|
||||
[System.Windows.MessageBox]::Show("Root folder $rootFolderSource does not exist", "Error", "OK", "Error")
|
||||
return
|
||||
}
|
||||
|
||||
if([IO.Directory]::Exists($rootFolderCompare) -eq $false)
|
||||
{
|
||||
[System.Windows.MessageBox]::Show("Root folder $rootFolderCompare does not exist", "Error", "OK", "Error")
|
||||
return
|
||||
}
|
||||
|
||||
$outputType = $global:cbCompareSave.SelectedValue
|
||||
Save-Setting "Compare" "SaveType" $outputType
|
||||
|
||||
$compResultValues = @()
|
||||
|
||||
foreach($item in ($global:dgObjectsToCompare.ItemsSource | where Selected -eq $true))
|
||||
{
|
||||
Write-Status "Compare $($item.ObjectType.Title) objects" -Force -SkipLog
|
||||
Write-Log "----------------------------------------------------------------"
|
||||
Write-Log "Compare $($item.ObjectType.Title) objects"
|
||||
Write-Log "----------------------------------------------------------------"
|
||||
|
||||
$folderSource = Join-Path $rootFolderSource $item.ObjectType.Id
|
||||
$folderCompare = Join-Path $rootFolderCompare $item.ObjectType.Id
|
||||
|
||||
if([IO.Directory]::Exists($folderSource))
|
||||
{
|
||||
Save-Setting "" "LastUsedFullPath" $folderSource
|
||||
|
||||
$fileCompareObjs = @(Get-GraphFileObjects $folderCompare -ObjectType $item.ObjectType)
|
||||
|
||||
foreach ($fileSourceObj in @(Get-GraphFileObjects $folderSource -ObjectType $item.ObjectType))
|
||||
{
|
||||
$objName = Get-GraphObjectName $fileSourceObj.Object $item.ObjectType
|
||||
if($txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
|
||||
{
|
||||
continue
|
||||
}
|
||||
|
||||
if(-not $fileSourceObj.Object.Id)
|
||||
{
|
||||
Write-Log "Object from file '$($fileSourceObj.FullName)' has no Id property. Compare not supported" 2
|
||||
continue
|
||||
}
|
||||
|
||||
$compareObject = $fileCompareObjs | Where { $_.Object.Id -eq $fileSourceObj.Object.Id }
|
||||
|
||||
if(-not $compareObject)
|
||||
{
|
||||
# Add objects that are exported but deleted
|
||||
Write-Log "Object '$($objName)' with id $($fileSourceObj.Object.Id) not found in Intune. Deleted?" 2
|
||||
$compareProperties = @([PSCustomObject]@{
|
||||
Object1Value = $null
|
||||
Object2Value = (Get-GraphObjectName $fileSourceObj.Object $fileSourceObj.ObjectType)
|
||||
Match = $false
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
$fileSourceObj.Object | Add-Member Noteproperty -Name "@ObjectFromFile" -Value $true -Force
|
||||
$compareObject.Object | Add-Member Noteproperty -Name "@ObjectFromFile" -Value $true -Force
|
||||
$compareProperties = Compare-Objects $compareObject.Object $fileSourceObj.Object $item.ObjectType
|
||||
}
|
||||
|
||||
$compareObjectsResult += [PSCustomObject]@{
|
||||
Object1 = $compareObject.Object
|
||||
Object2 = $fileSourceObj.Object
|
||||
ObjectType = $item.ObjectType
|
||||
Id = $fileSourceObj.Object.Id
|
||||
Result = $compareProperties
|
||||
}
|
||||
}
|
||||
|
||||
foreach($fileCompareObj in $fileCompareObjs)
|
||||
{
|
||||
# Add objects that were not exported in source folder
|
||||
if(($compareObjectsResult | Where { $_.Id -eq $fileCompareObj.Object.Id})) { continue }
|
||||
|
||||
$objName = Get-GraphObjectName $fileCompareObj.Object $item.ObjectType
|
||||
if($txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
|
||||
{
|
||||
continue
|
||||
}
|
||||
|
||||
$compareObjectsResult += [PSCustomObject]@{
|
||||
Object1 = $fileCompareObj.Object
|
||||
Object2 = $null
|
||||
ObjectType = $item.ObjectType
|
||||
Id = $fileCompareObj.Object.Id
|
||||
Result = @([PSCustomObject]@{
|
||||
Object1Value = (Get-GraphObjectName $fileCompareObj.Object $item.ObjectType)
|
||||
Object2Value = $null
|
||||
Match = $false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if($outputType -eq "objectType")
|
||||
{
|
||||
$compResultValues = @()
|
||||
}
|
||||
|
||||
foreach($compObj in @($compareObjectsResult | Where { $_.ObjectType.Id -eq $item.ObjectType.Id }))
|
||||
{
|
||||
$objName = Get-GraphObjectName (?? $compObj.Object1 $compObj.Object2) $item.ObjectType
|
||||
foreach($compValue in $compObj.Result)
|
||||
{
|
||||
$compResultValues += [PSCustomObject]@{
|
||||
ObjectName = $objName
|
||||
Id = $compObj.Id
|
||||
Type = $compObj.ObjectType.Title
|
||||
ODataType = $compObj.Object1.'@OData.Type'
|
||||
Property = $compValue.PropertyName
|
||||
Value1 = $compValue.Object1Value
|
||||
Value2 = $compValue.Object2Value
|
||||
Category = $compValue.Category
|
||||
SubCategory = $compValue.SubCategory
|
||||
Match = $compValue.Match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($outputType -eq "objectType")
|
||||
{
|
||||
Save-BulkCompareResults $compResultValues (Join-Path $folderSource "Compare_$(((Get-Date).ToString("yyyyMMdd-HHmm"))).csv") $compareProps
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Log "Folder $folderSource not found. Skipping compare" 2
|
||||
}
|
||||
}
|
||||
|
||||
if($outputType -eq "all" -and $compResultValues.Count -gt 0)
|
||||
{
|
||||
Save-BulkCompareResults $compResultValues (Join-Path $rootFolderSource "Compare_$(((Get-Date).ToString("yyyyMMDD-HHmm"))).csv") $compareProps
|
||||
}
|
||||
|
||||
Write-Log "****************************************************************"
|
||||
Write-Log "Bulk compare Exported Folders finished"
|
||||
Write-Log "****************************************************************"
|
||||
Write-Status ""
|
||||
if($compareObjectsResult.Count -eq 0)
|
||||
{
|
||||
[System.Windows.MessageBox]::Show("No objects were comparced. Verify folder and exported files", "Error", "OK", "Error")
|
||||
}
|
||||
}
|
||||
|
||||
function Save-BulkCompareResults
|
||||
{
|
||||
param($compResultValues, $file, $props)
|
||||
@@ -1064,6 +1303,11 @@ function Compare-ObjectsBasedonDocumentation
|
||||
$val1 = $prop.$settingsValue
|
||||
$prop2 = $docObj2.Settings | Where { $_.EntityKey -eq $prop.EntityKey -and $_.Category -eq $prop.Category -and $_.SubCategory -eq $prop.SubCategory -and $_.Enabled -eq $prop.Enabled }
|
||||
$val2 = $prop2.$settingsValue
|
||||
if($val1 -isnot [array] -and $val2 -is [array] -and $val2.Count -gt 1)
|
||||
{
|
||||
Write-Log "Multiple compare results returend for $($prop.Name). Using first result" 2
|
||||
$val2 = $val2[0]
|
||||
}
|
||||
Add-CompareProperty $prop.Name $val1 $val2 $prop.Category $prop.SubCategory
|
||||
}
|
||||
|
||||
@@ -1075,7 +1319,13 @@ function Compare-ObjectsBasedonDocumentation
|
||||
$addedProperties += ($prop.EntityKey + $prop.Category + $prop.SubCategory)
|
||||
$val2 = $prop.$settingsValue
|
||||
$prop2 = $docObj1.Settings | Where { $_.EntityKey -eq $prop.EntityKey -and $_.Category -eq $prop.Category -and $_.SubCategory -eq $prop.SubCategory -and $_.Enabled -eq $prop.Enabled }
|
||||
$val1 = $prop2.$settingsValue
|
||||
$val1 = $prop2.$settingsValue
|
||||
if($val2 -isnot [array] -and $val1 -is [array] -and $val1.Count -gt 1)
|
||||
{
|
||||
Write-Log "Multiple compare results returend for $($prop.Name). Using first result" 2
|
||||
$val1 = $val1[0]
|
||||
}
|
||||
|
||||
Add-CompareProperty $prop.Name $val1 $val2 $prop.Category $prop.SubCategory
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#https://docs.microsoft.com/en-us/office/vba/api/overview/word
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'1.0.4'
|
||||
'1.0.5'
|
||||
}
|
||||
|
||||
function Invoke-InitializeModule
|
||||
@@ -117,6 +117,7 @@ function Invoke-WordPreProcessItems
|
||||
Save-Setting "Documentation" "WordExportProperties" $global:cbWordDocumentationProperties.SelectedValue
|
||||
Save-Setting "Documentation" "WordCustomDisplayProperties" $global:txtWordCustomProperties.Text
|
||||
Save-Setting "Documentation" "WordDocumentTemplate" $global:txtWordDocumentTemplate.Text
|
||||
Save-Setting "Documentation" "WordDocumentName" $global:txtWordDocumentName.Text
|
||||
|
||||
Save-Setting "Documentation" "WordAddCategories" $global:chkWordAddCategories.IsChecked
|
||||
Save-Setting "Documentation" "WordAddSubCategories" $global:chkWordAddSubCategories.IsChecked
|
||||
|
||||
@@ -9,7 +9,7 @@ Module for listing Intune assignments
|
||||
#>
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'1.0.1'
|
||||
'1.0.2'
|
||||
}
|
||||
|
||||
function Invoke-InitializeModule
|
||||
@@ -170,6 +170,7 @@ function Get-EMIntuneAssignments
|
||||
}
|
||||
else
|
||||
{
|
||||
$assignmentObj = $assignment.target.groupId
|
||||
Write-Warning "Could not find a group with ID $($assignment.target.groupId)"
|
||||
}
|
||||
$included = $assignment.target.'@odata.type' -eq "#microsoft.graph.groupAssignmentTarget"
|
||||
|
||||
@@ -10,7 +10,7 @@ This module manages Authentication for the application with MSAL. It is also res
|
||||
#>
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'3.0.5'
|
||||
'3.3.0'
|
||||
}
|
||||
|
||||
$global:msalAuthenticator = $null
|
||||
@@ -18,10 +18,28 @@ function Invoke-InitializeModule
|
||||
{
|
||||
$script:MSALAllApps = @()
|
||||
$global:MSALToken = $null
|
||||
$global:MSALAuthority = $null
|
||||
$global:MSALTenantId = $null
|
||||
$script:AccessableTenants = $null
|
||||
$global:SkipTokenCacheHelperEx = $null
|
||||
|
||||
$script:lstAADEnvironments = @(
|
||||
[PSCustomObject]@{
|
||||
Name = "Azure AD Public"
|
||||
Value = "public"
|
||||
URL = "login.microsoftonline.com"
|
||||
},
|
||||
[PSCustomObject]@{
|
||||
Name = "Azure AD US Government"
|
||||
Value = "usGov"
|
||||
URL = "login.microsoftonline.us"
|
||||
},
|
||||
[PSCustomObject]@{
|
||||
Name = "Azure AD China"
|
||||
Value = "china"
|
||||
URL = "login.partner.microsoftonline.cn"
|
||||
}
|
||||
)
|
||||
|
||||
$global:appSettingSections += (New-Object PSObject -Property @{
|
||||
Title = "MSAL"
|
||||
Id = "MSAL"
|
||||
@@ -68,6 +86,14 @@ function Invoke-InitializeModule
|
||||
Description = "Request Azure AD Role read permission when getting the token. This can be use to resolve the SIDs to Azure Roles for the wids property on the Access Token. Note: This might trigger a consent prompt"
|
||||
}) "MSAL"
|
||||
|
||||
Add-SettingsObject (New-Object PSObject -Property @{
|
||||
Title = "Azure Login"
|
||||
Key = "AzureLogin"
|
||||
Type = "List"
|
||||
ItemsSource = $script:lstAADEnvironments
|
||||
DefaultValue = "public"
|
||||
}) "MSAL"
|
||||
|
||||
Add-MSALPrereq
|
||||
|
||||
#$script:MSALDLLMissing = $true #!!!!
|
||||
@@ -91,9 +117,19 @@ function Get-MSALAuthenticationObject
|
||||
$global:msalAuthenticator
|
||||
}
|
||||
|
||||
function Invoke-SettingsUpdated
|
||||
{
|
||||
Initialize-MSALSettings
|
||||
}
|
||||
|
||||
function Initialize-MSALSettings
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
function Clear-MSALCurentUserVaiables
|
||||
{
|
||||
$global:MSALAuthority = $null
|
||||
$global:MSALTenantId = $null
|
||||
}
|
||||
|
||||
function Get-MSALCurrentApp
|
||||
@@ -485,19 +521,42 @@ function Get-MsalAuthenticationToken
|
||||
$authResult
|
||||
}
|
||||
|
||||
function Get-MSALLoginEnvironment
|
||||
{
|
||||
$loginValue = Get-SettingValue "AzureLogin" "public"
|
||||
$loginEnv = $script:lstAADEnvironments | Where value -eq $loginValue
|
||||
return (?? $loginEnv.Environment "login.microsoftonline.com")
|
||||
}
|
||||
function Get-MSALApp
|
||||
{
|
||||
param($appInfo)
|
||||
param($appInfo, $loginHint)
|
||||
|
||||
$msalApp = $script:MSALAllApps | Where { $_.ClientId -eq $appInfo.ClientID -and (-not $appInfo.RedirectUri -or $_.AppConfig.RedirectUri -eq $appInfo.RedirectUri)}
|
||||
|
||||
if(-not $msalApp)
|
||||
|
||||
$tenant = ?? $appInfo.TenantId "organizations"
|
||||
|
||||
if($loginHint.Environment)
|
||||
{
|
||||
Write-Log "Add MSAL App $($appInfo.ClientID) $((?? $appInfo.TenantId $appInfo.Authority))"
|
||||
$authority = "https://$($loginHint.Environment)/$tenant/"
|
||||
}
|
||||
elseif($appInfo.Authority)
|
||||
{
|
||||
$authority = $appInfo.Authority
|
||||
}
|
||||
else
|
||||
{
|
||||
$authority = "https://$((Get-MSALLoginEnvironment))/$tenant/"
|
||||
}
|
||||
|
||||
if(-not $msalApp -or $msalApp.Authority -ne $authority)
|
||||
{
|
||||
Write-Log "Add MSAL App $($appInfo.ClientID) $authority"
|
||||
$appBuilder = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($appInfo.ClientID)
|
||||
|
||||
if($appInfo.TenantId) { [void]$appBuilder.WithAuthority("https://login.microsoftonline.com/$($appInfo.TenantId)/") }
|
||||
elseif ($appInfo.Authority) { [void]$appBuilder.WithAuthority($appInfo.Authority) }
|
||||
|
||||
[void]$appBuilder.WithAuthority($authority)
|
||||
#if($appInfo.TenantId) { [void]$appBuilder.WithAuthority("https://$((?? $loginHint.Environment (Get-MSALLoginEnvironment)))/$($appInfo.TenantId)/") }
|
||||
#elseif ($appInfo.Authority) { [void]$appBuilder.WithAuthority($appInfo.Authority) }
|
||||
|
||||
if($appInfo.RedirectUri) { [void]$appBuilder.WithRedirectUri($appInfo.RedirectUri) }
|
||||
|
||||
[void] $appBuilder.WithClientName("CloudAPIPowerShellManagement")
|
||||
@@ -514,6 +573,18 @@ function Get-MSALApp
|
||||
return $msalApp
|
||||
}
|
||||
|
||||
function Get-MSALAppAuthority
|
||||
{
|
||||
try
|
||||
{
|
||||
([uri]$global:MSALApp.Authority).Authority
|
||||
}
|
||||
catch
|
||||
{
|
||||
Get-MSALLoginEnvironment
|
||||
}
|
||||
}
|
||||
|
||||
function Connect-MSALUser
|
||||
{
|
||||
param(
|
||||
@@ -529,7 +600,9 @@ function Connect-MSALUser
|
||||
[switch]
|
||||
$Interactive,
|
||||
|
||||
$Account
|
||||
$Account,
|
||||
|
||||
$Tenant
|
||||
)
|
||||
|
||||
# No login during first time the app is started
|
||||
@@ -543,11 +616,10 @@ function Connect-MSALUser
|
||||
return
|
||||
}
|
||||
|
||||
if(-not $global:appObj.TenantId -and -not $global:appObj.Authority)
|
||||
{
|
||||
Write-Log "Tenant id/Authority is missing. Cannot authenticate" 3
|
||||
return
|
||||
}
|
||||
#if(-not $global:appObj.TenantId -and -not $global:appObj.Authority)
|
||||
#{
|
||||
# Write-Log "Tenant id/Authority is missing. Cannot authenticate" 3
|
||||
#}
|
||||
|
||||
if ($global:SkipTokenCacheHelperEx -ne $true -and -not ("TokenCacheHelperEx" -as [type]))
|
||||
{
|
||||
@@ -598,20 +670,21 @@ function Connect-MSALUser
|
||||
$Scopes = [String[]]$reqScopes
|
||||
}
|
||||
|
||||
$global:MSALApp = Get-MSALApp $global:appObj
|
||||
$global:MSALApp = Get-MSALApp $global:appObj $Account
|
||||
$loginHint = ""
|
||||
|
||||
$global:MSALAccounts = $global:MSALApp.GetAccountsAsync().GetAwaiter().GetResult()
|
||||
if($Account)
|
||||
{
|
||||
$loginHint = $global:MSALAccounts | Where UserName -eq $Account
|
||||
if($global:MSALToken -and $global:MSALToken.Account.UserName -ne $Account)
|
||||
$userName = ?? $Account.UserName $Account
|
||||
$loginHint = $global:MSALAccounts | Where UserName -eq $userName
|
||||
if($global:MSALToken -and $global:MSALToken.Account.UserName -ne $userName)
|
||||
{
|
||||
# We're logging in with someone else...
|
||||
Clear-MSALCurentUserVaiables
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# If we force interactive login the skip setting loginHint to force the user select account
|
||||
if(-not $loginHint -and $Interactive -ne $true)
|
||||
{
|
||||
@@ -640,8 +713,8 @@ function Connect-MSALUser
|
||||
|
||||
$prompConsent = $false
|
||||
$authResult = $null
|
||||
$tenantId = $global:appObj.TenantId
|
||||
$authority = ?? $global:MSALAuthority $global:appObj.Authority
|
||||
$tenantId = ?? $global:MSALTenantId $global:appObj.TenantId
|
||||
#$authority = ?? $global:MSALApp.Authority $global:appObj.Authority
|
||||
|
||||
try
|
||||
{
|
||||
@@ -652,8 +725,8 @@ function Connect-MSALUser
|
||||
{
|
||||
$aquireTokenObj = $global:MSALApp.AcquireTokenSilent($Scopes, $loginHint)
|
||||
if($ForceRefresh) { [void]$aquireTokenObj.WithForceRefresh($ForceRefresh) }
|
||||
if ($tenantId) { [void] $aquireTokenObj.WithAuthority("https://login.microsoftonline.com/$($TenantId)/") }
|
||||
if ($authority) { [void]$aquireTokenObj.WithAuthority($authority) }
|
||||
if ($tenantId) { [void]$aquireTokenObj.WithAuthority("https://$((Get-MSALAppAuthority))/$($tenantId)/") }
|
||||
else { [void]$aquireTokenObj.WithAuthority($global:MSALApp.Authority) }
|
||||
|
||||
$authResult = Get-MsalAuthenticationToken $aquireTokenObj
|
||||
|
||||
@@ -709,7 +782,10 @@ function Connect-MSALUser
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to perform silent login" $_.Exception
|
||||
}
|
||||
|
||||
# Interactive login is only allowed once the app has started. Skip if silent login failed during startup
|
||||
if($global:MainAppStarted -and ((-not $authResult -and $Silent -ne $true) -or $prompConsent))
|
||||
@@ -726,12 +802,12 @@ function Connect-MSALUser
|
||||
if ($tenantId)
|
||||
{
|
||||
Write-Log "Tenant id: $tenantId"
|
||||
[void] $aquireTokenObj.WithAuthority("https://login.microsoftonline.com/$tenantId)/")
|
||||
[void]$aquireTokenObj.WithAuthority("https://$((Get-MSALAppAuthority))/$tenantId/")
|
||||
}
|
||||
elseif ($authority)
|
||||
else
|
||||
{
|
||||
Write-Log "Authority: $authority"
|
||||
[void]$aquireTokenObj.WithAuthority($authority)
|
||||
Write-Log "Authority: $($global:MSALApp.Authority)"
|
||||
[void]$aquireTokenObj.WithAuthority($global:MSALApp.Authority)
|
||||
}
|
||||
|
||||
if($loginHintName)
|
||||
@@ -786,8 +862,8 @@ function Connect-MSALUser
|
||||
|
||||
# Can we reuse the app used for login?
|
||||
$appBuilder = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($global:appObj.ClientID)
|
||||
if($tenantId) { [void]$appBuilder.WithAuthority("https://login.microsoftonline.com/$($tenantId)") }
|
||||
elseif ($authority) { [void]$appBuilder.WithAuthority($authority) }
|
||||
if($tenantId) { [void]$appBuilder.WithAuthority("https://$((Get-MSALAppAuthority))/$($tenantId)") }
|
||||
else { [void]$appBuilder.WithAuthority($global:MSALApp.Authority) }
|
||||
if($global:appObj.RedirectUri) { [void]$appBuilder.WithRedirectUri($global:appObj.RedirectUri) }
|
||||
$app = $appBuilder.Build()
|
||||
|
||||
@@ -977,7 +1053,7 @@ function Get-MSALProfileEllipse
|
||||
Write-Status "Logging in with $($this.Tag.UserName)"
|
||||
Hide-Popup
|
||||
Clear-MSALCurentUserVaiables
|
||||
Connect-MSALUser -Account $this.Tag.UserName
|
||||
Connect-MSALUser -Account $this.Tag #!!!.UserName
|
||||
|
||||
if($global:curObjectType)
|
||||
{
|
||||
@@ -1200,7 +1276,7 @@ function Get-MSALProfileEllipse
|
||||
Write-Status "Logging in with $($this.Tag.UserName)"
|
||||
Hide-Popup
|
||||
Clear-MSALCurentUserVaiables
|
||||
Connect-MSALUser -Account $this.Tag.UserName
|
||||
Connect-MSALUser -Account $this.Tag #!!!.UserName
|
||||
|
||||
if($global:curObjectType)
|
||||
{
|
||||
@@ -1311,9 +1387,9 @@ function Get-MSALProfileEllipse
|
||||
$lnkButton.add_Click({
|
||||
Write-Status "Logging in to $($this.Tag.DisplayName)"
|
||||
# Set authority to selected tenant
|
||||
$global:MSALAuthority = "https://login.microsoftonline.com/$($this.Tag.tenantId)/"
|
||||
Hide-Popup
|
||||
Connect-MSALUser -Account $global:MSALToken.Account.Username
|
||||
$global:MSALTenantId = $this.Tag.tenantId
|
||||
Hide-Popup
|
||||
Connect-MSALUser -Account ($global:MSALAccounts | Where UserName -eq $global:MSALToken.Account.Username)
|
||||
|
||||
if($global:curObjectType)
|
||||
{
|
||||
|
||||
@@ -10,13 +10,14 @@ This module manages Microsoft Grap fuctions like calling APIs, managing graph ob
|
||||
#>
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'3.1.7'
|
||||
'3.1.8'
|
||||
}
|
||||
|
||||
$global:MSGraphGlobalApps = @(
|
||||
#Authority="https://login.microsoftonline.com/organizations/"
|
||||
(New-Object PSObject -Property @{Name="";ClientId="";RedirectUri="";Authority=""}),
|
||||
(New-Object PSObject -Property @{Name="Microsoft Intune PowerShell";ClientId="d1ddf0e4-d672-4dae-b554-9d5bdfd93547";RedirectUri="urn:ietf:wg:oauth:2.0:oob";Authority="https://login.microsoftonline.com/organizations/"}),
|
||||
(New-Object PSObject -Property @{Name="Microsoft Graph PowerShell";ClientId="14d82eec-204b-4c2f-b7e8-296a70dab67e";RedirectUri="https://login.microsoftonline.com/common/oauth2/nativeclient";Authority="https://login.microsoftonline.com/organizations/"})
|
||||
(New-Object PSObject -Property @{Name="Microsoft Intune PowerShell";ClientId="d1ddf0e4-d672-4dae-b554-9d5bdfd93547";RedirectUri="urn:ietf:wg:oauth:2.0:oob"; }),
|
||||
(New-Object PSObject -Property @{Name="Microsoft Graph PowerShell";ClientId="14d82eec-204b-4c2f-b7e8-296a70dab67e";RedirectUri="https://login.microsoftonline.com/common/oauth2/nativeclient";})
|
||||
)
|
||||
|
||||
function Invoke-InitializeModule
|
||||
@@ -150,6 +151,15 @@ function Invoke-InitializeModule
|
||||
DefaultValue = $false
|
||||
Description = "This will enable the option to update/replace an existing object during import"
|
||||
}) "ImportExport"
|
||||
|
||||
|
||||
Add-SettingsObject (New-Object PSObject -Property @{
|
||||
Title = "Add ID to export file"
|
||||
Key = "AddIDToExportFile"
|
||||
Type = "Boolean"
|
||||
DefaultValue = $false
|
||||
Description = "This will add object ID to the export file to support objects with the same name e.g. ObjectName_ObjectId.json"
|
||||
}) "ImportExport"
|
||||
}
|
||||
|
||||
function Get-GraphAppInfo
|
||||
@@ -192,6 +202,16 @@ function Invoke-GraphAuthenticationUpdated
|
||||
$global:migFileObj = $null
|
||||
}
|
||||
|
||||
function Invoke-SettingsUpdated
|
||||
{
|
||||
Initialize-GraphSettings
|
||||
}
|
||||
|
||||
function Initialize-GraphSettings
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
function Invoke-GraphRequest
|
||||
{
|
||||
param (
|
||||
@@ -296,7 +316,7 @@ function Invoke-GraphRequest
|
||||
{
|
||||
# Code to test paging - Force each page to size specified in top parameter below
|
||||
# Kept for reference
|
||||
|
||||
|
||||
if(($url.IndexOf('?')) -eq -1)
|
||||
{
|
||||
$url = "$($url.Trim())?"
|
||||
@@ -844,7 +864,8 @@ function Show-GraphBulkExportForm
|
||||
|
||||
Set-XamlProperty $script:exportForm "txtExportPath" "Text" (?? (Get-Setting "" "LastUsedRoot") (Get-SettingValue "RootFolder"))
|
||||
Set-XamlProperty $script:exportForm "chkAddCompanyName" "IsChecked" (Get-SettingValue "AddCompanyName")
|
||||
Set-XamlProperty $script:exportForm "chkExportAssignments" "IsChecked" (Get-SettingValue "ExportAssignments")
|
||||
Set-XamlProperty $script:exportForm "chkExportAssignments" "IsChecked" (Get-SettingValue "ExportAssignments")
|
||||
#Set-XamlProperty $script:exportForm "txtExportNameFilter" "Text" (Get-Setting "" "ExportNameFilter")
|
||||
|
||||
Add-XamlEvent $script:exportForm "browseExportPath" "add_click" ({
|
||||
$folder = Get-Folder (Get-XamlProperty $script:exportForm "txtExportPath" "Text") "Select root folder for export"
|
||||
@@ -903,6 +924,9 @@ function Show-GraphBulkExportForm
|
||||
Write-Log "****************************************************************"
|
||||
Write-Log "Start bulk export"
|
||||
Write-Log "****************************************************************"
|
||||
|
||||
$global:AADObjectCache = $null
|
||||
|
||||
foreach($item in $script:exportObjects)
|
||||
{
|
||||
if($item.Selected -ne $true) { continue }
|
||||
@@ -911,6 +935,10 @@ function Show-GraphBulkExportForm
|
||||
Write-Log "Export $($item.ObjectType.Title) objects"
|
||||
Write-Log "----------------------------------------------------------------"
|
||||
|
||||
$txtNameFilter = $global:txtExportNameFilter.Text.Trim()
|
||||
Save-Setting "" "ExportNameFilter" $txtNameFilter
|
||||
if($txtNameFilter) { Write-Log "Name filter: $txtNameFilter" }
|
||||
|
||||
try
|
||||
{
|
||||
$folder = Get-GraphObjectFolder $item.ObjectType (Get-XamlProperty $script:exportForm "txtExportPath" "Text") (Get-XamlProperty $script:exportForm "chkAddObjectType" "IsChecked") (Get-XamlProperty $script:exportForm "chkAddCompanyName" "IsChecked")
|
||||
@@ -918,7 +946,14 @@ function Show-GraphBulkExportForm
|
||||
$objects = @(Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType)
|
||||
foreach($obj in $objects)
|
||||
{
|
||||
Write-Status "Export $($item.Title): $((Get-GraphObjectName $obj.Object $obj.ObjectType))" -Force
|
||||
$objName = Get-GraphObjectName $obj.Object $obj.ObjectType
|
||||
|
||||
if($txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
|
||||
{
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Status "Export $($item.Title): $objName" -Force
|
||||
Export-GraphObject $obj.Object $item.ObjectType $folder
|
||||
}
|
||||
Save-Setting "" "LastUsedFullPath" $folder
|
||||
@@ -1064,7 +1099,8 @@ function Show-GraphBulkImportForm
|
||||
Set-XamlProperty $script:importForm "chkImportScopes" "IsChecked" (Get-SettingValue "ImportScopeTags")
|
||||
Set-XamlProperty $script:importForm "cbImportType" "ItemsSource" $script:lstImportTypes
|
||||
Set-XamlProperty $script:importForm "cbImportType" "SelectedValue" (Get-SettingValue "ImportType" "alwaysImport")
|
||||
|
||||
#Set-XamlProperty $script:importForm "txtImportNameFilter" "Text" (Get-Setting "" "ImportNameFilter")
|
||||
|
||||
if((Get-SettingValue "AllowUpdate") -eq $true)
|
||||
{
|
||||
Set-XamlProperty $script:importForm "lblImportType" "Visibility" "Visible"
|
||||
@@ -1140,6 +1176,10 @@ function Show-GraphBulkImportForm
|
||||
Get-GraphDependencyDefaultObjects
|
||||
$importedObjects = 0
|
||||
|
||||
$txtNameFilter = $global:txtImportNameFilter.Text.Trim()
|
||||
Save-Setting "" "ImportNameFilter" $txtNameFilter
|
||||
if($txtNameFilter) { Write-Log "Name filter: $txtNameFilter" }
|
||||
|
||||
$allowUpdate = ((Get-SettingValue "AllowUpdate") -eq $true)
|
||||
|
||||
foreach($item in ($script:importObjects | where Selected -eq $true | sort-object -property @{e={$_.ObjectType.ImportOrder}}))
|
||||
@@ -1172,6 +1212,13 @@ function Show-GraphBulkImportForm
|
||||
|
||||
foreach ($fileObj in @($filesToImport))
|
||||
{
|
||||
$objName = Get-GraphObjectName $fileObj.Object $item.ObjectType
|
||||
|
||||
if($txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
|
||||
{
|
||||
continue
|
||||
}
|
||||
|
||||
if($allowUpdate -and $global:cbImportType.SelectedValue -ne "alwaysImport" -and $graphObjects -and (Reset-GraphObjet $fileObj $graphObjects))
|
||||
{
|
||||
$importedObjects++
|
||||
@@ -1274,6 +1321,8 @@ function Show-GraphBulkDeleteForm
|
||||
$script:deleteForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\BulkDeleteForm.xaml") -AddVariables
|
||||
if(-not $script:deleteForm) { return }
|
||||
|
||||
Set-XamlProperty $script:deleteForm "txtDeleteNameFilter" "Text" (Get-Setting "" "txtDeleteNameFilter")
|
||||
|
||||
$script:deleteObjects = @()
|
||||
foreach($objType in $global:lstMenuItems.ItemsSource)
|
||||
{
|
||||
@@ -1343,13 +1392,24 @@ function Show-GraphBulkDeleteForm
|
||||
Write-Log "Delete $($item.ObjectType.Title) objects"
|
||||
Write-Log "----------------------------------------------------------------"
|
||||
|
||||
$txtNameFilter = $global:txtDeleteNameFilter.Text.Trim()
|
||||
Save-Setting "" "DeleteNameFilter" $txtNameFilter
|
||||
if($txtNameFilter) { Write-Log "Name filter: $txtNameFilter" }
|
||||
|
||||
try
|
||||
{
|
||||
Write-Status "Get $($item.Title) objects" -Force
|
||||
$objects = @(Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType)
|
||||
foreach($obj in $objects)
|
||||
{
|
||||
Write-Status "Delete $($item.Title): $((Get-GraphObjectName $obj.Object $obj.ObjectType))" -Force
|
||||
$objName = Get-GraphObjectName $obj.Object $obj.ObjectType
|
||||
|
||||
if($txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
|
||||
{
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Status "Delete $($item.Title): $objName" -Force -SkipLog
|
||||
Remove-GraphObject $obj.Object $obj.ObjectType $folder
|
||||
}
|
||||
}
|
||||
@@ -1875,7 +1935,7 @@ function Add-GraphMigrationObject
|
||||
$global:migFileObj = $null
|
||||
}
|
||||
|
||||
if(-not $global:migFileObj)
|
||||
if(-not $global:migFileObj -or ([IO.File]::Exists($migFileName) -eq $false))
|
||||
{
|
||||
if(-not ([IO.File]::Exists($migFileName)))
|
||||
{
|
||||
@@ -2238,7 +2298,13 @@ function Export-GraphObject
|
||||
Remove-Property $obj "Assignments"
|
||||
}
|
||||
|
||||
$obj | ConvertTo-Json -Depth 20 | Out-File -LiteralPath ([IO.Path]::Combine($exportFolder, (Remove-InvalidFileNameChars "$((Get-GraphObjectName $obj $objectType)).json"))) -Force
|
||||
$fileName = Get-GraphObjectName $obj $objectType
|
||||
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
|
||||
{
|
||||
$fileName = ($fileName + "_" + $obj.Id)
|
||||
}
|
||||
|
||||
$obj | ConvertTo-Json -Depth 20 | Out-File -LiteralPath ([IO.Path]::Combine($exportFolder, (Remove-InvalidFileNameChars "$($fileName).json"))) -Force
|
||||
|
||||
if($objectType.PostExportCommand)
|
||||
{
|
||||
|
||||
28
README.md
28
README.md
@@ -23,9 +23,9 @@ Before logging on:
|
||||
|
||||
* The app will use the Intune PowerShell Azure Enterprise Application by default but request all permissions required by the script. The will most likely cause a consent prompt since it uses more permission than the Intune module. Enable **Use Default Permissions** in Settings to only request the current permissions granted to the Enterprise App.
|
||||
**Note:** Using default permission might reduce functionality e.g. permissions for one or more object types might be missing
|
||||
* Enable **Get Tenant List** in Settings if accessing multiple environments with the same account. This might cause a Consent prompt
|
||||
* Enable **Get Tenant List** in Settings if accessing multiple environments with the same account e.g. a guest account in other tenants. This might cause a Consent prompt
|
||||
|
||||
Start the script by running **Start.cmd**, **Start-WithConsole.cmd** or **Start-IntuneManagement.ps1**. **Start-WithConsole.cmd** will leave the command prompt window open so you can see the log while running the app.
|
||||
Start the script by running **Start.cmd**, **Start-WithJson.cmd**, **Start-WithConsole.cmd** or **Start-IntuneManagement.ps1**. **Start-WithConsole.cmd** will leave the command prompt window open so you can see the log while running the app.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -171,6 +171,28 @@ See [Change Log](ReleaseNotes.md) for more information
|
||||
## Authentication
|
||||
See [MSAL Info](MSALInfo.md) for more information about authentication
|
||||
|
||||
## Settings
|
||||
|
||||
Settings for the script is default stored in the registry. However, the script supports settings to be stored in a json file so it can be copied between computers. Registry settings can be exported in the Settings dialog.
|
||||
|
||||
To use settings based on a json file:
|
||||
|
||||
```
|
||||
Start-IntuneManagement.ps1 -JSonSettings [-JSonFile <PathToFile>]
|
||||
```
|
||||
|
||||
If only -JSonSettings is used the script will use the default json setting file:
|
||||
|
||||
```
|
||||
%LOCALAPPDATA%\CloudAPIPowerShellManagement\Settings.json
|
||||
```
|
||||
|
||||
Use -JSonFile for custom location of the file
|
||||
|
||||
Start-WithJson.cmd is included as an example on how to start the script with json settings.
|
||||
|
||||
**Note:** If the file can't be created, the script will revert back registry. Make sure that the script can write to the file. It is not recommended to store the file in a folder that requires UAC to get write permissions.
|
||||
|
||||
## Supported Intune objects
|
||||
* App Configurations (App and Device)
|
||||
* App Protection
|
||||
@@ -235,7 +257,7 @@ Android Store Apps are **not** imported. The Create API is documented in Microso
|
||||
|
||||
Using multiple tenants support causes multiple logins/consent prompts the first time if 'Microsoft Graph PowerShell' is used. Querying the API for tenant list uses a different scope that is not included by default in the 'Microsoft Graph PowerShell' app.
|
||||
|
||||
~~Using multiple tenants support *might* cause and endless loop in the login screen and cause duplicate accounts in token cache. Actual cause is not found yet but it happens on rare occasions and it looks like it happens when a guest account is used. Workaround: Cancel the login, restart the script, logout and restart the script again.~~ - Not seen this in a long time. Please create issue if this happens
|
||||
~~Using multiple tenants support *might* cause and endless loop in the login screen and cause duplicate accounts in token cache. Actual cause is not found yet but it happens on rare occasions and it looks like it happens when a guest account is used. Workaround: Cancel the login, restart the script, logout and restart the script again.~~ - Not seen this in a long time. Please create an issue if this happens
|
||||
|
||||
When multi tenant settings is Enabled/Disabled, the Profile Info is not updated until the account is changed or app is restarted. Profile Info popup is built after logon.
|
||||
|
||||
|
||||
@@ -1,5 +1,45 @@
|
||||
# Release Notes
|
||||
|
||||
## 3.3.0 (Beta) - 2021-10-17
|
||||
|
||||
This is a **BETA** release. It contains core changes for Authentication and Settings management. Please report any issues [here](https://github.com/Micke-K/IntuneManagement/issues).
|
||||
|
||||
**New features**
|
||||
|
||||
- Support for Settings in Json files
|
||||
Settings can now be stored in json files and copied between devices.
|
||||
|
||||
See [Readme](README.md#Settings) on how to use this feature
|
||||
This is based on [Issue 33](https://github.com/Micke-K/IntuneManagement/issues/33)
|
||||
|
||||
- Bulk Compare for exported folders
|
||||
|
||||
The tool can now compare two exported folders
|
||||
This is based on [Issue 32](https://github.com/Micke-K/IntuneManagement/issues/32)
|
||||
|
||||
- Support for Azure AD US Government cloud and Azure AD China cloud. Default is Azure AD Public cloud.
|
||||
|
||||
Change cloud in Settings
|
||||
**Note:** This is a major change to the authentication. This may have an impact if a custom configured Azure app is used.
|
||||
This is based on [Issue 26](https://github.com/Micke-K/IntuneManagement/issues/26). Please report any problem, progress or testing with US Government/China cloud or if there are any issues when a custom configured Azure app is used.
|
||||
|
||||
- Export can now add Id to the name of the backup file
|
||||
|
||||
This can be used if there are multiple objects with the same name.
|
||||
|
||||
This can be enabled in Settings. Backup file name will be <Name>_<Id>.json.
|
||||
|
||||
- Export/Import/Compare/Delete now supports name filter
|
||||
Objects are filtered based on escaped RegEx -nomatch expression so wildcards are not supported.
|
||||
|
||||
- IntuneAssignments report will now include the id of deleted groups
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fixed an issue in Export. Groups were not exported if exporting multiple times and multiple folders during the same session.
|
||||
* Fixed an issue in Compare where the csv file was not stored in the correct folder
|
||||
* Fixed an issue in Compare where the comparing object may return System[]. This can happen if the generated files has multiple documentation items for a property. First result will be used.
|
||||
|
||||
## 3.2.3 - 2021-10-07
|
||||
|
||||
**New features**
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
[CmdletBinding(SupportsShouldProcess=$True)]
|
||||
param(
|
||||
[switch]
|
||||
$ShowConsoleWindow
|
||||
$ShowConsoleWindow,
|
||||
[switch]
|
||||
$JSonSettings,
|
||||
[string]
|
||||
$JSonFile
|
||||
)
|
||||
Import-Module ($PSScriptRoot + "\CloudAPIPowerShellManagement.psd1") -Force
|
||||
Initialize-CloudAPIManagement -View "IntuneGraphAPI" -ShowConsoleWindow:($ShowConsoleWindow)
|
||||
Initialize-CloudAPIManagement -View "IntuneGraphAPI" -ShowConsoleWindow:($ShowConsoleWindow) -JSonSettings:($JSonSettings) -JSonFile $JSonFile
|
||||
|
||||
1
Start-WithJson.cmd
Normal file
1
Start-WithJson.cmd
Normal file
@@ -0,0 +1 @@
|
||||
cmd /c powershell -ex bypass -File "%~DP0Start-IntuneManagement.ps1" -JSonSettings
|
||||
@@ -1,19 +1,31 @@
|
||||
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5,5,5,5" Grid.IsSharedSizeScope='True'>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="TitleColumn" />
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='0' Margin="0,0,5,0" VerticalAlignment="Top">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='0' Margin="0,0,5,0" >
|
||||
<Label Content="Name filter" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Specify name filter for the objects to delete" />
|
||||
</StackPanel>
|
||||
<TextBox Text="" Name="txtDeleteNameFilter" Grid.Column='1' Grid.Row='0' Margin="0,5,0,0" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='1' Margin="5,0,5,0" VerticalAlignment="Top" Grid.ColumnSpan="2">
|
||||
<Label Content="Objects to delete" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="All objects of the seleted types will be deleted" Margin="0,2,0,0" />
|
||||
</StackPanel>
|
||||
<DataGrid Name="dgBulkDeleteObjects" Grid.Row='1' CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="White" Margin="0,0,0,5">
|
||||
|
||||
<DataGrid Name="dgBulkDeleteObjects" Grid.Row='2' Grid.ColumnSpan="2" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="White" Margin="0,0,0,5">
|
||||
</DataGrid>
|
||||
|
||||
<StackPanel Name="spDeleteSubMenu" Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row='2' Grid.ColumnSpan='2' >
|
||||
<StackPanel Name="spDeleteSubMenu" Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row='3' Grid.ColumnSpan='2' >
|
||||
<Button Name="btnDelete" Content="Delete" Width='100' Margin="5,0,0,0" />
|
||||
<Button Name="btnClose" Content="Close" Width='100' Margin="5,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -37,24 +37,30 @@
|
||||
<Button Grid.Column="2" Name="browseExportPath" Padding="5,0,5,0" Width="50" ToolTip="Browse for folder">...</Button>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='1' Margin="0,0,5,0" >
|
||||
<Label Content="Name filter" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Specify name filter for the objects to export" />
|
||||
</StackPanel>
|
||||
<TextBox Text="" Name="txtExportNameFilter" Grid.Column='1' Grid.Row='1' Margin="0,5,0,0" />
|
||||
|
||||
<!-- Force object type in name by setting it to true and disable the checkbox. Leave it on for information -->
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='1' Margin="0,0,5,0">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='2' Margin="0,0,5,0">
|
||||
<Label Content="Add object name to path" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="This will export all objects to a sub-directory of the export path with name based on object type" />
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column='1' Grid.Row='1' Name='chkAddObjectType' VerticalAlignment="Center" IsEnabled="false" IsChecked="true" />
|
||||
<CheckBox Grid.Column='1' Grid.Row='2' Name='chkAddObjectType' VerticalAlignment="Center" IsEnabled="false" IsChecked="true" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='2' Margin="0,0,5,0">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='3' Margin="0,0,5,0">
|
||||
<Label Content="Export Assignments" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Export object assignments" />
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column='1' Grid.Row='2' Name='chkExportAssignments' VerticalAlignment="Center" IsChecked="true" />
|
||||
<CheckBox Grid.Column='1' Grid.Row='3' Name='chkExportAssignments' VerticalAlignment="Center" IsChecked="true" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='3' Margin="0,0,5,0">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='4' Margin="0,0,5,0">
|
||||
<Label Content="Add company name to path" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="This will add the company name in Azure to the export path" />
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column='1' Grid.Row='3' Name='chkAddCompanyName' VerticalAlignment="Center" IsChecked="true" />
|
||||
<CheckBox Grid.Column='1' Grid.Row='4' Name='chkAddCompanyName' VerticalAlignment="Center" IsChecked="true" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row='1' VerticalAlignment="Stretch">
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="TitleColumn" />
|
||||
@@ -37,39 +38,45 @@
|
||||
<TextBox Text="" Name="txtImportPath" />
|
||||
<Button Grid.Column="2" Name="browseImportPath" Padding="5,0,5,0" Width="50" ToolTip="Browse for folder">...</Button>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='1' Margin="0,0,5,0" >
|
||||
<Label Content="Name filter" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Specify name filter for the objects to import" />
|
||||
</StackPanel>
|
||||
<TextBox Text="" Name="txtImportNameFilter" Grid.Column='1' Grid.Row='1' Margin="0,5,0,0" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='1' Margin="0,0,5,0" Name="spMigrationTableInfo">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='2' Margin="0,0,5,0" Name="spMigrationTableInfo">
|
||||
<Label Content="Migration Table" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="This contains information about the exported environment e.g. Groups, ScopeTags etc. Note: This is only used when import object from a different tenant" />
|
||||
</StackPanel>
|
||||
<Label Grid.Column='1' Grid.Row='1' Name="lblMigrationTableInfo" />
|
||||
<Label Grid.Column='1' Grid.Row='2' Name="lblMigrationTableInfo" />
|
||||
|
||||
<!-- Force object type in name by setting it to true and disable the checkbox. Leave it on for information -->
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='2' Margin="0,0,5,0">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='3' Margin="0,0,5,0">
|
||||
<Label Content="Add object name to path" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="This will import objects from a sub-directory of the import path with name based on object type" />
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column='1' Grid.Row='2' Name='chkAddObjectType' VerticalAlignment="Center" IsEnabled="false" IsChecked="true" />
|
||||
<CheckBox Grid.Column='1' Grid.Row='3' Name='chkAddObjectType' VerticalAlignment="Center" IsEnabled="false" IsChecked="true" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='3' Margin="0,0,5,0">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='4' Margin="0,0,5,0">
|
||||
<Label Content="Import Scope (Tags)" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="This will import ScopeTags. The ScopeTags must exist in the target environment before thay can be assigned during import of an object" />
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column='1' Grid.Row='3' Name='chkImportScopes' VerticalAlignment="Center" IsChecked="true" />
|
||||
<CheckBox Grid.Column='1' Grid.Row='4' Name='chkImportScopes' VerticalAlignment="Center" IsChecked="true" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='4' Margin="0,0,5,0">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='5' Margin="0,0,5,0">
|
||||
<Label Content="Import Assignments" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Import object assignments. Note: This will create groups that don't exist in the target environment" />
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column='1' Grid.Row='4' Name='chkImportAssignments' VerticalAlignment="Center" IsChecked="true" />
|
||||
<CheckBox Grid.Column='1' Grid.Row='5' Name='chkImportAssignments' VerticalAlignment="Center" IsChecked="true" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='5' Margin="0,0,5,0">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='6' Margin="0,0,5,0">
|
||||
<Label Content="Replace Dependecy IDs" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Replaces IDs of dependency objects e.g. App Config references Applications. Increases import time but makes sure objects are imported correctly. Note: References objects must exist!" />
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column='1' Grid.Row='5' Name='chkReplaceDependencyIDs' VerticalAlignment="Center" IsChecked="true" />
|
||||
<CheckBox Grid.Column='1' Grid.Row='6' Name='chkReplaceDependencyIDs' VerticalAlignment="Center" IsChecked="true" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='6' Margin="0,0,5,0" Name="lblImportType" Visibility="Collapsed">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='7' Margin="0,0,5,0" Name="lblImportType" Visibility="Collapsed">
|
||||
<Label Content="Import Type" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}">
|
||||
<Rectangle.ToolTip>
|
||||
@@ -83,7 +90,7 @@
|
||||
</Rectangle.ToolTip>
|
||||
</Rectangle>
|
||||
</StackPanel>
|
||||
<ComboBox Name="cbImportType" Margin="0,5,0,0" MinWidth="250" Grid.Row='6' Grid.Column="1" HorizontalAlignment="Left"
|
||||
<ComboBox Name="cbImportType" Margin="0,5,0,0" MinWidth="250" Grid.Row='7' Grid.Column="1" HorizontalAlignment="Left"
|
||||
DisplayMemberPath="Name" SelectedValuePath="Value" Visibility="Collapsed" />
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -8,11 +8,17 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,5,0" >
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='0' Margin="0,0,5,0" >
|
||||
<Label Content="Name filter" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Specify name filter for the objects to compare" />
|
||||
</StackPanel>
|
||||
<TextBox Text="" Name="txtCompareNameFilter" Grid.Column='1' Grid.Row='0' Margin="0,5,5,0" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='1' Margin="0,0,5,0" >
|
||||
<Label Content="Export root" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="The root folder where exported files are stored. This sould be the Company Name folder if it was included in the export." />
|
||||
</StackPanel>
|
||||
<Grid Grid.Column='1' Grid.Row='0' Margin="0,5,5,0">
|
||||
<Grid Grid.Column='1' Grid.Row='1' Margin="0,5,5,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
|
||||
51
Xaml/CompareExportedFilesOptions.xaml
Normal file
51
Xaml/CompareExportedFilesOptions.xaml
Normal file
@@ -0,0 +1,51 @@
|
||||
<Grid Name="grdImportProperties" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="TitleColumn" />
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='0' Margin="0,0,5,0" >
|
||||
<Label Content="Name filter" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Specify name filter for the objects to compare" />
|
||||
</StackPanel>
|
||||
<TextBox Text="" Name="txtCompareNameFilter" Grid.Column='1' Grid.Row='0' Margin="0,5,5,0" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='1' Margin="0,0,5,0" >
|
||||
<Label Content="Source root" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="The root folder where exported files are stored. This sould be the Company Name folder if it was included in the export." />
|
||||
</StackPanel>
|
||||
<Grid Grid.Column='1' Grid.Row='1' Margin="0,5,5,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox Text="" Name="txtExportPathSource" />
|
||||
<Button Grid.Column="2" Name="browseExportPathSource" Padding="5,2,5,2" Width="50" ToolTip="Browse for folder">...</Button>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row='2' Margin="0,0,5,0" >
|
||||
<Label Content="Compare root" />
|
||||
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="The folder with exported object that should be compared with the source" />
|
||||
</StackPanel>
|
||||
<Grid Grid.Column='1' Grid.Row='2' Margin="0,5,5,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox Text="" Name="txtExportPathCompare" />
|
||||
<Button Grid.Column="2" Name="browseExportPathCompare" Padding="5,2,5,2" Width="50" ToolTip="Browse for folder">...</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -25,7 +25,8 @@
|
||||
</ScrollViewer>
|
||||
|
||||
<StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal" Margin="0,5,0,5">
|
||||
<Button Name="btnSave" Width="100">Save</Button>
|
||||
<Button Name="btnExport" Width="100">Export</Button>
|
||||
<Button Name="btnSave" Width="100" Margin="15,0,0,0">Save</Button>
|
||||
<Button Name="btnClose" Width="100" Margin="15,0,0,0">Close</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user