Beta release
This commit is contained in:
Mikael Karlsson
2021-10-17 14:02:08 +11:00
parent 5976b0bffd
commit 4add87884a
18 changed files with 959 additions and 107 deletions

Binary file not shown.

View File

@@ -69,7 +69,11 @@ function Initialize-CloudAPIManagement
[string] [string]
$View = "", $View = "",
[switch] [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'" $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 Hide-Console
} }
if($JSonSettings -eq $true)
{
$global:UseJSonSettings = $true
$global:JSonSettingFile = $JSonFile
}
else
{
$global:UseJSonSettings = $false
}
$global:txtSplashText.Text = "Unblock files" $global:txtSplashText.Text = "Unblock files"
[System.Windows.Forms.Application]::DoEvents() [System.Windows.Forms.Application]::DoEvents()
Unblock-AllFiles $PSScriptRoot Unblock-AllFiles $PSScriptRoot

328
Core.psm1
View File

@@ -6,13 +6,12 @@ Core UI and Settings fatures for the CloudAPIPowerShellManager solution
This module handles the WPF UI This module handles the WPF UI
.NOTES .NOTES
Version: 3.1.0
Author: Mikael Karlsson Author: Mikael Karlsson
#> #>
function Get-ModuleVersion function Get-ModuleVersion
{ {
'3.1.7' '3.3.0'
} }
function Start-CoreApp function Start-CoreApp
@@ -35,10 +34,18 @@ function Start-CoreApp
# Load all modules in the Modules folder # Load all modules in the Modules folder
$global:modulesPath = [IO.Path]::GetDirectoryName($PSCommandPath) + "\Extensions" $global:modulesPath = [IO.Path]::GetDirectoryName($PSCommandPath) + "\Extensions"
#Import-Module ($PSScriptRoot + "\Core.psm1") -Force -Global
Add-DefaultSettings Add-DefaultSettings
if($global:UseJSonSettings -eq $true)
{
Initialize-JsonSettings
}
if($global:UseJSonSettings -eq $false)
{
Write-Log "Use settings in registry"
}
Write-Log "#####################################################################################" Write-Log "#####################################################################################"
Write-Log "Application started" Write-Log "Application started"
Write-Log "#####################################################################################" Write-Log "#####################################################################################"
@@ -53,7 +60,7 @@ function Start-CoreApp
exit 1 exit 1
} }
$global:Debug = Get-SettingValue "Debug" Initialize-Settings
$global:currentViewObject = $null $global:currentViewObject = $null
$global:FirstTimeRunning = ((Get-Setting "" "FirstTimeRunning" "true") -eq "true") $global:FirstTimeRunning = ((Get-Setting "" "FirstTimeRunning" "true") -eq "true")
$global:MainAppStarted = $false $global:MainAppStarted = $false
@@ -795,34 +802,230 @@ function Expand-FileName
#endregion #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 function Save-Setting
{ {
param($SubPath, $Key, $Value, $Type = "String") param($SubPath = "", $Key = "", $Value, $Type = "String")
$regPath = Get-RegPath $SubPath if($global:JsonSettingsObj -and $global:JSonSettingFile)
if((Test-Path $regPath) -eq $false)
{ {
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 function Get-Setting
{ {
param($SubPath, $Key, $defautValue) param($SubPath = "", $Key = "", $defautValue)
try if(-not $key)
{ {
$val = Get-ItemPropertyValue -Path (Get-RegPath $SubPath) -Name $Key -ErrorAction SilentlyContinue 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) if(-not $val)
{ {
$defautValue $defautValue
@@ -845,6 +1048,77 @@ function Get-RegPath
$path $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 #endregion
#region Setting functions #region Setting functions
@@ -1028,13 +1302,30 @@ function Show-SettingsForm
Add-XamlEvent $settingsForm "btnSave" "Add_Click" ({ Add-XamlEvent $settingsForm "btnSave" "Add_Click" ({
Save-AllSettings Save-AllSettings
$global:Debug = Get-SettingValue "Debug"
}) })
Add-XamlEvent $settingsForm "btnClose" "Add_Click" ({ Add-XamlEvent $settingsForm "btnClose" "Add_Click" ({
Show-ModalObject 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" $tmp = $global:appSettingSections | Where-Object Id -eq "General"
if($tmp.Values.Count -gt 0) if($tmp.Values.Count -gt 0)
{ {
@@ -1174,6 +1465,9 @@ function Save-AllSettings
{ {
& $global:currentViewObject.ViewInfo.SaveSettings & $global:currentViewObject.ViewInfo.SaveSettings
} }
Initialize-Settings -Updated
Start-Sleep -Seconds 1 # It goes to quick...ToDo: Do this in a better way Start-Sleep -Seconds 1 # It goes to quick...ToDo: Do this in a better way
Write-Status "" Write-Status ""
} }

View File

@@ -11,7 +11,7 @@ Objects can be compared based on Properties or Documentatation info.
function Get-ModuleVersion function Get-ModuleVersion
{ {
'1.0.7' '1.0.8'
} }
function Invoke-InitializeModule function Invoke-InitializeModule
@@ -50,16 +50,16 @@ function Add-CompareProvider
if($global:compareProviders.Count -eq 0) if($global:compareProviders.Count -eq 0)
{ {
$global:compareProviders += [PSCustomObject]@{ $global:compareProviders += [PSCustomObject]@{
Name = "Exported File" Name = "Intune Objects with Exported Files"
Value = "export" Value = "export"
ObjectCompare = { Compare-ObjectsBasedonProperty @args } ObjectCompare = { Compare-ObjectsBasedonProperty @args }
BulkCompare = { Start-BulkCompareExportObjects @args } BulkCompare = { Start-BulkCompareExportObjects @args }
ProviderOptions = "CompareExportOptions" ProviderOptions = "CompareExportOptions"
Activate = { Invoke-ActivateCompareExportObjects @args } Activate = { Invoke-ActivateCompareWithExportObjects @args }
} }
$global:compareProviders += [PSCustomObject]@{ $global:compareProviders += [PSCustomObject]@{
Name = "Named Objects" Name = "Named Objects in Intune"
Value = "name" Value = "name"
BulkCompare = { Start-BulkCompareNamedObjects @args } BulkCompare = { Start-BulkCompareNamedObjects @args }
ProviderOptions = "CompareNamedOptions" ProviderOptions = "CompareNamedOptions"
@@ -67,6 +67,15 @@ function Add-CompareProvider
RemoveProperties = @("Id") 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]@{ $global:compareProviders += [PSCustomObject]@{
Name = "Existing objects" Name = "Existing objects"
Value = "existing" Value = "existing"
@@ -269,7 +278,9 @@ function Set-CompareProviderOptions
} }
} }
function Invoke-ActivateCompareExportObjects # Compare Intune object with exported folder
function Invoke-ActivateCompareWithExportObjects
{ {
param($providerOptions, $firstTime) 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 function Invoke-ActivateCompareNamesObjects
{ {
param($providerOptions, $firstTime) param($providerOptions, $firstTime)
@@ -468,6 +512,8 @@ function Start-BulkCompareExportObjects
Write-Log "Start bulk Exported Objects compare" Write-Log "Start bulk Exported Objects compare"
Write-Log "****************************************************************" Write-Log "****************************************************************"
$compareObjectsResult = @() $compareObjectsResult = @()
$txtNameFilter = (Get-XamlProperty $global:ccContentProviderOptions.Content "txtCompareNameFilter" "Text").Trim()
$rootFolder = (Get-XamlProperty $global:ccContentProviderOptions.Content "txtExportPath" "Text") $rootFolder = (Get-XamlProperty $global:ccContentProviderOptions.Content "txtExportPath" "Text")
$compareProps = $script:defaultCompareProps $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 Write-Log "Object from file '$($fileObj.FullName)' has no Id property. Compare not supported" 2
continue 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 } $curObject = $graphObjects | Where { $_.Object.Id -eq $fileObj.Object.Id }
if(-not $curObject) if(-not $curObject)
{ {
# Add objects that are exported but deleted # 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]@{ $compareProperties = @([PSCustomObject]@{
Object1Value = $null Object1Value = $null
Object2Value = (Get-GraphObjectName $fileObj.Object $item.ObjectType) Object2Value = (Get-GraphObjectName $fileObj.Object $item.ObjectType)
@@ -554,13 +608,19 @@ function Start-BulkCompareExportObjects
# Add objects that are not exported # Add objects that are not exported
if(($compareObjectsResult | Where { $_.Id -eq $graphObj.Id})) { continue } 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]@{ $compareObjectsResult += [PSCustomObject]@{
Object1 = $curObject.Object Object1 = $curObject.Object
Object2 = $null Object2 = $null
ObjectType = $item.ObjectType ObjectType = $item.ObjectType
Id = $graphObj.Id Id = $graphObj.Id
Result = @([PSCustomObject]@{ Result = @([PSCustomObject]@{
Object1Value = (Get-GraphObjectName $graphObj.Object $item.ObjectType) Object1Value = $objName
Object2Value = $null Object2Value = $null
Match = $false Match = $false
}) })
@@ -594,18 +654,18 @@ function Start-BulkCompareExportObjects
if($outputType -eq "objectType") 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 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) 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 "****************************************************************" 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 function Save-BulkCompareResults
{ {
param($compResultValues, $file, $props) param($compResultValues, $file, $props)
@@ -1064,6 +1303,11 @@ function Compare-ObjectsBasedonDocumentation
$val1 = $prop.$settingsValue $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 } $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 $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 Add-CompareProperty $prop.Name $val1 $val2 $prop.Category $prop.SubCategory
} }
@@ -1076,6 +1320,12 @@ function Compare-ObjectsBasedonDocumentation
$val2 = $prop.$settingsValue $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 } $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 Add-CompareProperty $prop.Name $val1 $val2 $prop.Category $prop.SubCategory
} }
} }

View File

@@ -3,7 +3,7 @@
#https://docs.microsoft.com/en-us/office/vba/api/overview/word #https://docs.microsoft.com/en-us/office/vba/api/overview/word
function Get-ModuleVersion function Get-ModuleVersion
{ {
'1.0.4' '1.0.5'
} }
function Invoke-InitializeModule function Invoke-InitializeModule
@@ -117,6 +117,7 @@ function Invoke-WordPreProcessItems
Save-Setting "Documentation" "WordExportProperties" $global:cbWordDocumentationProperties.SelectedValue Save-Setting "Documentation" "WordExportProperties" $global:cbWordDocumentationProperties.SelectedValue
Save-Setting "Documentation" "WordCustomDisplayProperties" $global:txtWordCustomProperties.Text Save-Setting "Documentation" "WordCustomDisplayProperties" $global:txtWordCustomProperties.Text
Save-Setting "Documentation" "WordDocumentTemplate" $global:txtWordDocumentTemplate.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" "WordAddCategories" $global:chkWordAddCategories.IsChecked
Save-Setting "Documentation" "WordAddSubCategories" $global:chkWordAddSubCategories.IsChecked Save-Setting "Documentation" "WordAddSubCategories" $global:chkWordAddSubCategories.IsChecked

View File

@@ -9,7 +9,7 @@ Module for listing Intune assignments
#> #>
function Get-ModuleVersion function Get-ModuleVersion
{ {
'1.0.1' '1.0.2'
} }
function Invoke-InitializeModule function Invoke-InitializeModule
@@ -170,6 +170,7 @@ function Get-EMIntuneAssignments
} }
else else
{ {
$assignmentObj = $assignment.target.groupId
Write-Warning "Could not find a group with ID $($assignment.target.groupId)" Write-Warning "Could not find a group with ID $($assignment.target.groupId)"
} }
$included = $assignment.target.'@odata.type' -eq "#microsoft.graph.groupAssignmentTarget" $included = $assignment.target.'@odata.type' -eq "#microsoft.graph.groupAssignmentTarget"

View File

@@ -10,7 +10,7 @@ This module manages Authentication for the application with MSAL. It is also res
#> #>
function Get-ModuleVersion function Get-ModuleVersion
{ {
'3.0.5' '3.3.0'
} }
$global:msalAuthenticator = $null $global:msalAuthenticator = $null
@@ -18,10 +18,28 @@ function Invoke-InitializeModule
{ {
$script:MSALAllApps = @() $script:MSALAllApps = @()
$global:MSALToken = $null $global:MSALToken = $null
$global:MSALAuthority = $null $global:MSALTenantId = $null
$script:AccessableTenants = $null $script:AccessableTenants = $null
$global:SkipTokenCacheHelperEx = $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 @{ $global:appSettingSections += (New-Object PSObject -Property @{
Title = "MSAL" Title = "MSAL"
Id = "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" 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" }) "MSAL"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Azure Login"
Key = "AzureLogin"
Type = "List"
ItemsSource = $script:lstAADEnvironments
DefaultValue = "public"
}) "MSAL"
Add-MSALPrereq Add-MSALPrereq
#$script:MSALDLLMissing = $true #!!!! #$script:MSALDLLMissing = $true #!!!!
@@ -91,9 +117,19 @@ function Get-MSALAuthenticationObject
$global:msalAuthenticator $global:msalAuthenticator
} }
function Invoke-SettingsUpdated
{
Initialize-MSALSettings
}
function Initialize-MSALSettings
{
}
function Clear-MSALCurentUserVaiables function Clear-MSALCurentUserVaiables
{ {
$global:MSALAuthority = $null $global:MSALTenantId = $null
} }
function Get-MSALCurrentApp function Get-MSALCurrentApp
@@ -485,19 +521,42 @@ function Get-MsalAuthenticationToken
$authResult $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 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)} $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) $appBuilder = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($appInfo.ClientID)
if($appInfo.TenantId) { [void]$appBuilder.WithAuthority("https://login.microsoftonline.com/$($appInfo.TenantId)/") } [void]$appBuilder.WithAuthority($authority)
elseif ($appInfo.Authority) { [void]$appBuilder.WithAuthority($appInfo.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) } if($appInfo.RedirectUri) { [void]$appBuilder.WithRedirectUri($appInfo.RedirectUri) }
[void] $appBuilder.WithClientName("CloudAPIPowerShellManagement") [void] $appBuilder.WithClientName("CloudAPIPowerShellManagement")
@@ -514,6 +573,18 @@ function Get-MSALApp
return $msalApp return $msalApp
} }
function Get-MSALAppAuthority
{
try
{
([uri]$global:MSALApp.Authority).Authority
}
catch
{
Get-MSALLoginEnvironment
}
}
function Connect-MSALUser function Connect-MSALUser
{ {
param( param(
@@ -529,7 +600,9 @@ function Connect-MSALUser
[switch] [switch]
$Interactive, $Interactive,
$Account $Account,
$Tenant
) )
# No login during first time the app is started # No login during first time the app is started
@@ -543,11 +616,10 @@ function Connect-MSALUser
return return
} }
if(-not $global:appObj.TenantId -and -not $global:appObj.Authority) #if(-not $global:appObj.TenantId -and -not $global:appObj.Authority)
{ #{
Write-Log "Tenant id/Authority is missing. Cannot authenticate" 3 # Write-Log "Tenant id/Authority is missing. Cannot authenticate" 3
return #}
}
if ($global:SkipTokenCacheHelperEx -ne $true -and -not ("TokenCacheHelperEx" -as [type])) if ($global:SkipTokenCacheHelperEx -ne $true -and -not ("TokenCacheHelperEx" -as [type]))
{ {
@@ -598,14 +670,15 @@ function Connect-MSALUser
$Scopes = [String[]]$reqScopes $Scopes = [String[]]$reqScopes
} }
$global:MSALApp = Get-MSALApp $global:appObj $global:MSALApp = Get-MSALApp $global:appObj $Account
$loginHint = "" $loginHint = ""
$global:MSALAccounts = $global:MSALApp.GetAccountsAsync().GetAwaiter().GetResult() $global:MSALAccounts = $global:MSALApp.GetAccountsAsync().GetAwaiter().GetResult()
if($Account) if($Account)
{ {
$loginHint = $global:MSALAccounts | Where UserName -eq $Account $userName = ?? $Account.UserName $Account
if($global:MSALToken -and $global:MSALToken.Account.UserName -ne $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... # We're logging in with someone else...
Clear-MSALCurentUserVaiables Clear-MSALCurentUserVaiables
@@ -640,8 +713,8 @@ function Connect-MSALUser
$prompConsent = $false $prompConsent = $false
$authResult = $null $authResult = $null
$tenantId = $global:appObj.TenantId $tenantId = ?? $global:MSALTenantId $global:appObj.TenantId
$authority = ?? $global:MSALAuthority $global:appObj.Authority #$authority = ?? $global:MSALApp.Authority $global:appObj.Authority
try try
{ {
@@ -652,8 +725,8 @@ function Connect-MSALUser
{ {
$aquireTokenObj = $global:MSALApp.AcquireTokenSilent($Scopes, $loginHint) $aquireTokenObj = $global:MSALApp.AcquireTokenSilent($Scopes, $loginHint)
if($ForceRefresh) { [void]$aquireTokenObj.WithForceRefresh($ForceRefresh) } if($ForceRefresh) { [void]$aquireTokenObj.WithForceRefresh($ForceRefresh) }
if ($tenantId) { [void] $aquireTokenObj.WithAuthority("https://login.microsoftonline.com/$($TenantId)/") } if ($tenantId) { [void]$aquireTokenObj.WithAuthority("https://$((Get-MSALAppAuthority))/$($tenantId)/") }
if ($authority) { [void]$aquireTokenObj.WithAuthority($authority) } else { [void]$aquireTokenObj.WithAuthority($global:MSALApp.Authority) }
$authResult = Get-MsalAuthenticationToken $aquireTokenObj $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 # 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)) if($global:MainAppStarted -and ((-not $authResult -and $Silent -ne $true) -or $prompConsent))
@@ -726,12 +802,12 @@ function Connect-MSALUser
if ($tenantId) if ($tenantId)
{ {
Write-Log "Tenant id: $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" Write-Log "Authority: $($global:MSALApp.Authority)"
[void]$aquireTokenObj.WithAuthority($authority) [void]$aquireTokenObj.WithAuthority($global:MSALApp.Authority)
} }
if($loginHintName) if($loginHintName)
@@ -786,8 +862,8 @@ function Connect-MSALUser
# Can we reuse the app used for login? # Can we reuse the app used for login?
$appBuilder = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($global:appObj.ClientID) $appBuilder = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($global:appObj.ClientID)
if($tenantId) { [void]$appBuilder.WithAuthority("https://login.microsoftonline.com/$($tenantId)") } if($tenantId) { [void]$appBuilder.WithAuthority("https://$((Get-MSALAppAuthority))/$($tenantId)") }
elseif ($authority) { [void]$appBuilder.WithAuthority($authority) } else { [void]$appBuilder.WithAuthority($global:MSALApp.Authority) }
if($global:appObj.RedirectUri) { [void]$appBuilder.WithRedirectUri($global:appObj.RedirectUri) } if($global:appObj.RedirectUri) { [void]$appBuilder.WithRedirectUri($global:appObj.RedirectUri) }
$app = $appBuilder.Build() $app = $appBuilder.Build()
@@ -977,7 +1053,7 @@ function Get-MSALProfileEllipse
Write-Status "Logging in with $($this.Tag.UserName)" Write-Status "Logging in with $($this.Tag.UserName)"
Hide-Popup Hide-Popup
Clear-MSALCurentUserVaiables Clear-MSALCurentUserVaiables
Connect-MSALUser -Account $this.Tag.UserName Connect-MSALUser -Account $this.Tag #!!!.UserName
if($global:curObjectType) if($global:curObjectType)
{ {
@@ -1200,7 +1276,7 @@ function Get-MSALProfileEllipse
Write-Status "Logging in with $($this.Tag.UserName)" Write-Status "Logging in with $($this.Tag.UserName)"
Hide-Popup Hide-Popup
Clear-MSALCurentUserVaiables Clear-MSALCurentUserVaiables
Connect-MSALUser -Account $this.Tag.UserName Connect-MSALUser -Account $this.Tag #!!!.UserName
if($global:curObjectType) if($global:curObjectType)
{ {
@@ -1311,9 +1387,9 @@ function Get-MSALProfileEllipse
$lnkButton.add_Click({ $lnkButton.add_Click({
Write-Status "Logging in to $($this.Tag.DisplayName)" Write-Status "Logging in to $($this.Tag.DisplayName)"
# Set authority to selected tenant # Set authority to selected tenant
$global:MSALAuthority = "https://login.microsoftonline.com/$($this.Tag.tenantId)/" $global:MSALTenantId = $this.Tag.tenantId
Hide-Popup Hide-Popup
Connect-MSALUser -Account $global:MSALToken.Account.Username Connect-MSALUser -Account ($global:MSALAccounts | Where UserName -eq $global:MSALToken.Account.Username)
if($global:curObjectType) if($global:curObjectType)
{ {

View File

@@ -10,13 +10,14 @@ This module manages Microsoft Grap fuctions like calling APIs, managing graph ob
#> #>
function Get-ModuleVersion function Get-ModuleVersion
{ {
'3.1.7' '3.1.8'
} }
$global:MSGraphGlobalApps = @( $global:MSGraphGlobalApps = @(
#Authority="https://login.microsoftonline.com/organizations/"
(New-Object PSObject -Property @{Name="";ClientId="";RedirectUri="";Authority=""}), (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 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";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";})
) )
function Invoke-InitializeModule function Invoke-InitializeModule
@@ -150,6 +151,15 @@ function Invoke-InitializeModule
DefaultValue = $false DefaultValue = $false
Description = "This will enable the option to update/replace an existing object during import" Description = "This will enable the option to update/replace an existing object during import"
}) "ImportExport" }) "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 function Get-GraphAppInfo
@@ -192,6 +202,16 @@ function Invoke-GraphAuthenticationUpdated
$global:migFileObj = $null $global:migFileObj = $null
} }
function Invoke-SettingsUpdated
{
Initialize-GraphSettings
}
function Initialize-GraphSettings
{
}
function Invoke-GraphRequest function Invoke-GraphRequest
{ {
param ( param (
@@ -845,6 +865,7 @@ function Show-GraphBulkExportForm
Set-XamlProperty $script:exportForm "txtExportPath" "Text" (?? (Get-Setting "" "LastUsedRoot") (Get-SettingValue "RootFolder")) 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 "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" ({ Add-XamlEvent $script:exportForm "browseExportPath" "add_click" ({
$folder = Get-Folder (Get-XamlProperty $script:exportForm "txtExportPath" "Text") "Select root folder for export" $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 "****************************************************************"
Write-Log "Start bulk export" Write-Log "Start bulk export"
Write-Log "****************************************************************" Write-Log "****************************************************************"
$global:AADObjectCache = $null
foreach($item in $script:exportObjects) foreach($item in $script:exportObjects)
{ {
if($item.Selected -ne $true) { continue } if($item.Selected -ne $true) { continue }
@@ -911,6 +935,10 @@ function Show-GraphBulkExportForm
Write-Log "Export $($item.ObjectType.Title) objects" Write-Log "Export $($item.ObjectType.Title) objects"
Write-Log "----------------------------------------------------------------" Write-Log "----------------------------------------------------------------"
$txtNameFilter = $global:txtExportNameFilter.Text.Trim()
Save-Setting "" "ExportNameFilter" $txtNameFilter
if($txtNameFilter) { Write-Log "Name filter: $txtNameFilter" }
try try
{ {
$folder = Get-GraphObjectFolder $item.ObjectType (Get-XamlProperty $script:exportForm "txtExportPath" "Text") (Get-XamlProperty $script:exportForm "chkAddObjectType" "IsChecked") (Get-XamlProperty $script:exportForm "chkAddCompanyName" "IsChecked") $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) $objects = @(Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType)
foreach($obj in $objects) 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 Export-GraphObject $obj.Object $item.ObjectType $folder
} }
Save-Setting "" "LastUsedFullPath" $folder Save-Setting "" "LastUsedFullPath" $folder
@@ -1064,6 +1099,7 @@ function Show-GraphBulkImportForm
Set-XamlProperty $script:importForm "chkImportScopes" "IsChecked" (Get-SettingValue "ImportScopeTags") Set-XamlProperty $script:importForm "chkImportScopes" "IsChecked" (Get-SettingValue "ImportScopeTags")
Set-XamlProperty $script:importForm "cbImportType" "ItemsSource" $script:lstImportTypes Set-XamlProperty $script:importForm "cbImportType" "ItemsSource" $script:lstImportTypes
Set-XamlProperty $script:importForm "cbImportType" "SelectedValue" (Get-SettingValue "ImportType" "alwaysImport") 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) if((Get-SettingValue "AllowUpdate") -eq $true)
{ {
@@ -1140,6 +1176,10 @@ function Show-GraphBulkImportForm
Get-GraphDependencyDefaultObjects Get-GraphDependencyDefaultObjects
$importedObjects = 0 $importedObjects = 0
$txtNameFilter = $global:txtImportNameFilter.Text.Trim()
Save-Setting "" "ImportNameFilter" $txtNameFilter
if($txtNameFilter) { Write-Log "Name filter: $txtNameFilter" }
$allowUpdate = ((Get-SettingValue "AllowUpdate") -eq $true) $allowUpdate = ((Get-SettingValue "AllowUpdate") -eq $true)
foreach($item in ($script:importObjects | where Selected -eq $true | sort-object -property @{e={$_.ObjectType.ImportOrder}})) 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)) 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)) if($allowUpdate -and $global:cbImportType.SelectedValue -ne "alwaysImport" -and $graphObjects -and (Reset-GraphObjet $fileObj $graphObjects))
{ {
$importedObjects++ $importedObjects++
@@ -1274,6 +1321,8 @@ function Show-GraphBulkDeleteForm
$script:deleteForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\BulkDeleteForm.xaml") -AddVariables $script:deleteForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\BulkDeleteForm.xaml") -AddVariables
if(-not $script:deleteForm) { return } if(-not $script:deleteForm) { return }
Set-XamlProperty $script:deleteForm "txtDeleteNameFilter" "Text" (Get-Setting "" "txtDeleteNameFilter")
$script:deleteObjects = @() $script:deleteObjects = @()
foreach($objType in $global:lstMenuItems.ItemsSource) foreach($objType in $global:lstMenuItems.ItemsSource)
{ {
@@ -1343,13 +1392,24 @@ function Show-GraphBulkDeleteForm
Write-Log "Delete $($item.ObjectType.Title) objects" Write-Log "Delete $($item.ObjectType.Title) objects"
Write-Log "----------------------------------------------------------------" Write-Log "----------------------------------------------------------------"
$txtNameFilter = $global:txtDeleteNameFilter.Text.Trim()
Save-Setting "" "DeleteNameFilter" $txtNameFilter
if($txtNameFilter) { Write-Log "Name filter: $txtNameFilter" }
try try
{ {
Write-Status "Get $($item.Title) objects" -Force Write-Status "Get $($item.Title) objects" -Force
$objects = @(Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType) $objects = @(Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType)
foreach($obj in $objects) 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 Remove-GraphObject $obj.Object $obj.ObjectType $folder
} }
} }
@@ -1875,7 +1935,7 @@ function Add-GraphMigrationObject
$global:migFileObj = $null $global:migFileObj = $null
} }
if(-not $global:migFileObj) if(-not $global:migFileObj -or ([IO.File]::Exists($migFileName) -eq $false))
{ {
if(-not ([IO.File]::Exists($migFileName))) if(-not ([IO.File]::Exists($migFileName)))
{ {
@@ -2238,7 +2298,13 @@ function Export-GraphObject
Remove-Property $obj "Assignments" 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) if($objectType.PostExportCommand)
{ {

View File

@@ -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. * 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 **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 ## Documentation
@@ -171,6 +171,28 @@ See [Change Log](ReleaseNotes.md) for more information
## Authentication ## Authentication
See [MSAL Info](MSALInfo.md) for more information about 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 ## Supported Intune objects
* App Configurations (App and Device) * App Configurations (App and Device)
* App Protection * 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 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. 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.

View File

@@ -1,5 +1,45 @@
# Release Notes # 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 ## 3.2.3 - 2021-10-07
**New features** **New features**

View File

@@ -1,7 +1,11 @@
[CmdletBinding(SupportsShouldProcess=$True)] [CmdletBinding(SupportsShouldProcess=$True)]
param( param(
[switch] [switch]
$ShowConsoleWindow $ShowConsoleWindow,
[switch]
$JSonSettings,
[string]
$JSonFile
) )
Import-Module ($PSScriptRoot + "\CloudAPIPowerShellManagement.psd1") -Force 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
View File

@@ -0,0 +1 @@
cmd /c powershell -ex bypass -File "%~DP0Start-IntuneManagement.ps1" -JSonSettings

View File

@@ -1,19 +1,31 @@
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" <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'> HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5,5,5,5" Grid.IsSharedSizeScope='True'>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </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" /> <Label Content="Objects to delete" />
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="All objects of the seleted types will be deleted" Margin="0,2,0,0" /> <Rectangle Style="{DynamicResource InfoIcon}" ToolTip="All objects of the seleted types will be deleted" Margin="0,2,0,0" />
</StackPanel> </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> </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="btnDelete" Content="Delete" Width='100' Margin="5,0,0,0" />
<Button Name="btnClose" Content="Close" Width='100' Margin="5,0,0,0" /> <Button Name="btnClose" Content="Close" Width='100' Margin="5,0,0,0" />
</StackPanel> </StackPanel>

View File

@@ -37,24 +37,30 @@
<Button Grid.Column="2" Name="browseExportPath" Padding="5,0,5,0" Width="50" ToolTip="Browse for folder">...</Button> <Button Grid.Column="2" Name="browseExportPath" Padding="5,0,5,0" Width="50" ToolTip="Browse for folder">...</Button>
</Grid> </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 --> <!-- 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" /> <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" /> <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> </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" /> <Label Content="Export Assignments" />
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Export object assignments" /> <Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Export object assignments" />
</StackPanel> </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" /> <Label Content="Add company name to path" />
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="This will add the company name in Azure to the export path" /> <Rectangle Style="{DynamicResource InfoIcon}" ToolTip="This will add the company name in Azure to the export path" />
</StackPanel> </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 Grid.Row='1' VerticalAlignment="Stretch"> <Grid Grid.Row='1' VerticalAlignment="Stretch">

View File

@@ -15,6 +15,7 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="TitleColumn" /> <ColumnDefinition Width="Auto" SharedSizeGroup="TitleColumn" />
@@ -38,38 +39,44 @@
<Button Grid.Column="2" Name="browseImportPath" Padding="5,0,5,0" Width="50" ToolTip="Browse for folder">...</Button> <Button Grid.Column="2" Name="browseImportPath" Padding="5,0,5,0" Width="50" ToolTip="Browse for folder">...</Button>
</Grid> </Grid>
<StackPanel Orientation="Horizontal" Grid.Row='1' Margin="0,0,5,0" Name="spMigrationTableInfo"> <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='2' Margin="0,0,5,0" Name="spMigrationTableInfo">
<Label Content="Migration Table" /> <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" /> <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> </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 --> <!-- 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" /> <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" /> <Rectangle Style="{DynamicResource InfoIcon}" ToolTip="This will import objects from a sub-directory of the import path with name based on object type" />
</StackPanel> </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)" /> <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" /> <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> </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" /> <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" /> <Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Import object assignments. Note: This will create groups that don't exist in the target environment" />
</StackPanel> </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" /> <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!" /> <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> </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" /> <Label Content="Import Type" />
<Rectangle Style="{DynamicResource InfoIcon}"> <Rectangle Style="{DynamicResource InfoIcon}">
<Rectangle.ToolTip> <Rectangle.ToolTip>
@@ -83,7 +90,7 @@
</Rectangle.ToolTip> </Rectangle.ToolTip>
</Rectangle> </Rectangle>
</StackPanel> </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" /> DisplayMemberPath="Name" SelectedValuePath="Value" Visibility="Collapsed" />
</Grid> </Grid>

View File

@@ -8,11 +8,17 @@
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </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" /> <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." /> <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> </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> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="5" /> <ColumnDefinition Width="5" />

View 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>

View File

@@ -25,7 +25,8 @@
</ScrollViewer> </ScrollViewer>
<StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal" Margin="0,5,0,5"> <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> <Button Name="btnClose" Width="100" Margin="15,0,0,0">Close</Button>
</StackPanel> </StackPanel>
</Grid> </Grid>