mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 19:05:38 +02:00
IntuneManagement 4.0.0-beta1
This commit is contained in:
@@ -0,0 +1,670 @@
|
||||
# Details / object-view dialog (Show-IntuneManagerDetailedView + helpers).
|
||||
# Split out of IntuneManagerUIWPF.ps1 on 2026-06-03 to match the Avalonia tree's
|
||||
# per-feature layout (architecture rule R9 — keep the WPF shell from growing further).
|
||||
|
||||
function Get-DetailsSettingsSignature
|
||||
{
|
||||
param($Form)
|
||||
|
||||
if(-not $Form) { return "" }
|
||||
|
||||
$scopeTagIds = @()
|
||||
if($script:_scopeTagPropName -and $null -ne $script:_detailsAssignedIds) {
|
||||
$scopeTagIds = @($script:_detailsAssignedIds | Sort-Object)
|
||||
}
|
||||
|
||||
$descriptionEnabled = $false
|
||||
try { $descriptionEnabled = [bool]($script:UIProvider.GetXamlProperty($Form, "txtObjectDescription", "IsEnabled")) } catch {}
|
||||
|
||||
$signature = [PSCustomObject]@{
|
||||
Name = [string]($script:UIProvider.GetXamlProperty($Form, "txtObjectName", "Text"))
|
||||
DescriptionEnabled = $descriptionEnabled
|
||||
Description = if($descriptionEnabled) { [string]($script:UIProvider.GetXamlProperty($Form, "txtObjectDescription", "Text")) } else { $null }
|
||||
ScopeTagProperty = [string]$script:_scopeTagPropName
|
||||
ScopeTagIds = $scopeTagIds
|
||||
}
|
||||
|
||||
return ($signature | ConvertTo-Json -Depth 10 -Compress)
|
||||
}
|
||||
|
||||
function Update-DetailsColumnEditorBindings
|
||||
{
|
||||
param($Form)
|
||||
|
||||
if(-not $Form) { return }
|
||||
foreach($controlName in @("txtObjectColumnsProperty", "txtObjectColumnsHeader", "chkObjectColumnOverride")) {
|
||||
try {
|
||||
$ctl = $Form.FindName($controlName)
|
||||
if(-not $ctl) { continue }
|
||||
$expression = $ctl.GetBindingExpression([System.Windows.Controls.TextBox]::TextProperty)
|
||||
if(-not $expression -and $controlName -eq "chkObjectColumnOverride") {
|
||||
$expression = $ctl.GetBindingExpression([System.Windows.Controls.Primitives.ToggleButton]::IsCheckedProperty)
|
||||
}
|
||||
if($expression) { $expression.UpdateSource() }
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-DetailsColumnsSignature
|
||||
{
|
||||
param($Form)
|
||||
|
||||
if(-not $Form) { return "" }
|
||||
Update-DetailsColumnEditorBindings -Form $Form
|
||||
|
||||
$columns = @()
|
||||
foreach($col in @($script:colObjectProperties)) {
|
||||
if(-not $col) { continue }
|
||||
$columns += [PSCustomObject]@{
|
||||
Property = [string]$col.Property
|
||||
Header = [string]$col.Header
|
||||
}
|
||||
}
|
||||
|
||||
$signature = [PSCustomObject]@{
|
||||
OverrideDefaultColumns = [bool]($script:UIProvider.GetXamlProperty($Form, "chkObjectColumnOverride", "IsChecked"))
|
||||
Columns = $columns
|
||||
}
|
||||
|
||||
return ($signature | ConvertTo-Json -Depth 20 -Compress)
|
||||
}
|
||||
|
||||
function Test-DetailsViewHasUnsavedChanges
|
||||
{
|
||||
param($Form)
|
||||
|
||||
if(-not $Form) { return $false }
|
||||
|
||||
$settingsDirty = $false
|
||||
$columnsDirty = $false
|
||||
|
||||
try {
|
||||
if($script:_detailsInitialSettingsSignature) {
|
||||
$settingsDirty = ((Get-DetailsSettingsSignature -Form $Form) -ne $script:_detailsInitialSettingsSignature)
|
||||
}
|
||||
}
|
||||
catch { Write-LogDebug "Details settings dirty check failed: $($_.Exception.Message)" }
|
||||
|
||||
try {
|
||||
if($script:_detailsInitialColumnsSignature) {
|
||||
$columnsDirty = ((Get-DetailsColumnsSignature -Form $Form) -ne $script:_detailsInitialColumnsSignature)
|
||||
}
|
||||
}
|
||||
catch { Write-LogDebug "Details columns dirty check failed: $($_.Exception.Message)" }
|
||||
|
||||
return ($settingsDirty -or $columnsDirty)
|
||||
}
|
||||
|
||||
function Confirm-DetailsViewClose
|
||||
{
|
||||
if(-not (Test-DetailsViewHasUnsavedChanges -Form $script:detailsForm)) { return $true }
|
||||
|
||||
$result = $script:UIProvider.ShowMessageBox(
|
||||
"You have unsaved changes in Settings or Columns.`n`nClose without saving?",
|
||||
"Unsaved changes", "YesNo", "Warning")
|
||||
|
||||
return ($result -eq "Yes")
|
||||
}
|
||||
|
||||
function Show-IntuneManagerDetailedView
|
||||
{
|
||||
param(
|
||||
$FormTitle = "",
|
||||
[switch]$NoLoadFull)
|
||||
|
||||
$selectedItem = $script:dgIntuneManagerObjects.SelectedItem
|
||||
if(-not $selectedItem -and $script:dgIntuneManagerObjects.ItemsSource) {
|
||||
$selectedItem = @($script:dgIntuneManagerObjects.ItemsSource |
|
||||
Where-Object { $_.PSObject.Properties['IsSelected'] -and $_.IsSelected -eq $true } |
|
||||
Select-Object -First 1)
|
||||
if($selectedItem.Count -gt 0) { $selectedItem = $selectedItem[0] }
|
||||
}
|
||||
|
||||
if(-not $selectedItem) { return }
|
||||
if(-not $selectedItem.JsonObject) { return }
|
||||
|
||||
$script:detailsForm = $script:UIProvider.GetXamlObject(($script:AppUIRootFolder + "\Xaml\ObjectDetails.xaml"))
|
||||
if(-not $script:detailsForm) { return }
|
||||
$script:detailsForm.Tag = $selectedItem
|
||||
$script:detailsForm | Add-Member -MemberType ScriptMethod -Name ConfirmClose -Value { Confirm-DetailsViewClose } -Force
|
||||
|
||||
if(-not $FormTitle) { $FormTitle = $selectedItem.PolicyName }
|
||||
$objName = $selectedItem.Name
|
||||
if($objName)
|
||||
{
|
||||
$FormTitle = "$FormTitle - $objName"
|
||||
}
|
||||
|
||||
if($selectedItem.PolicyType.AddUIDetailsExtension) {
|
||||
$selectedItem.PolicyType.AddUIDetailsExtension($script:detailsForm)
|
||||
}
|
||||
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "txtValue", "Text", $selectedItem.JsonString)
|
||||
|
||||
if($selectedItem.PolicyType.AllowFullDetails -eq $false)
|
||||
{
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "btnFull", "Visibility", "Collapsed")
|
||||
}
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnCopy", "Add_Click", {
|
||||
$tmp = $script:detailsForm.FindName("txtValue")
|
||||
if($tmp.Text) { $tmp.Text | Set-Clipboard }
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnFull", "Add_Click", {
|
||||
$selectedItem = $script:detailsForm.Tag
|
||||
if(-not $selectedItem) { return }
|
||||
|
||||
Write-Status "Get full object $($selectedItem.Name)"
|
||||
[void]$selectedItem.Get()
|
||||
|
||||
if($selectedItem.IsFullObject)
|
||||
{
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "txtValue", "Text", $selectedItem.JsonString)
|
||||
}
|
||||
Write-Status ""
|
||||
})
|
||||
|
||||
#Settings tab
|
||||
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "txtObjectName", "Text", $selectedItem.Name)
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "txtObjectDescription", "Text", $selectedItem.Description)
|
||||
|
||||
# Snapshots for the save-confirmation dialog: it names the object and says
|
||||
# exactly what changed, computed against these open-time values. The
|
||||
# scope-tag pair resets here because the picker block below only runs for
|
||||
# types WITH a scope-tag property - without the reset a previous dialog's
|
||||
# sets would leak into this object's change summary.
|
||||
$script:_detailsOriginalName = [string]$selectedItem.Name
|
||||
$script:_detailsOriginalDesc = [string]$selectedItem.Description
|
||||
$script:_detailsOriginalAssignedIds = $null
|
||||
|
||||
# ---- Scope tags (dual-list) ----------------------------------------------
|
||||
# Type-level capability: _ScopeTagProperty defaults to "roleScopeTagIds" on every
|
||||
# IntunePolicyType, but a few types clear it. Whether the property currently exists
|
||||
# on the JSON is irrelevant — a user can assign tags to a policy that has none yet.
|
||||
$script:_scopeTagPropName = $null
|
||||
if($selectedItem.PolicyType.ScopeTagProperty) {
|
||||
$script:_scopeTagPropName = [string]$selectedItem.PolicyType.ScopeTagProperty
|
||||
}
|
||||
|
||||
if(-not $script:_scopeTagPropName) {
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "pnlScopeTagsLabel", "Visibility", "Collapsed")
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "grdScopeTags", "Visibility", "Collapsed")
|
||||
}
|
||||
else {
|
||||
# Source the full tag list from the warm dependency cache
|
||||
# (Initialize-TenantDependencyCache preloads it on auth). The cache list
|
||||
# includes the synthetic Default (Id=0). Falls back to a live call if the
|
||||
# cache is empty (e.g. a session that started before the preload landed).
|
||||
$allScopeTags = @()
|
||||
try {
|
||||
$depObjects = Get-GraphDependencySourceObjects $selectedItem -DefaultPoliciesOnly
|
||||
if($depObjects -and $depObjects.ContainsKey("ScopeTags")) {
|
||||
$allScopeTags = @($depObjects["ScopeTags"])
|
||||
}
|
||||
}
|
||||
catch { Write-LogDebug "Scope tag dependency cache lookup failed: $($_.Exception.Message)" }
|
||||
|
||||
if($allScopeTags.Count -eq 0) {
|
||||
$allScopeTags = @(Get-GraphPolicies -PolicyType "ScopeTags" -TokenId $selectedItem._TokenId)
|
||||
}
|
||||
|
||||
# Resolve currently-assigned IDs from the JSON. Stringify so the comparison
|
||||
# works regardless of whether the cache entry stores Id as int (synthetic
|
||||
# Default) or string (real tags from Graph). Safe when the property is
|
||||
# absent — PowerShell yields $null which the empty-array cast swallows.
|
||||
$assignedIdSet = [System.Collections.Generic.HashSet[string]]::new()
|
||||
$currentIds = $null
|
||||
if($selectedItem.Object -and
|
||||
$selectedItem.Object.PSObject.Properties[$script:_scopeTagPropName]) {
|
||||
$currentIds = $selectedItem.Object.$script:_scopeTagPropName
|
||||
}
|
||||
foreach($id in @($currentIds)) {
|
||||
if($null -ne $id) { [void]$assignedIdSet.Add([string]$id) }
|
||||
}
|
||||
|
||||
# Project to plain PSCustomObject {Id, Name} so the ListBox binding works
|
||||
# uniformly. The cache mixes two shapes: the synthetic Default is a real
|
||||
# PSCustomObject (renders fine), but real tags are IntunePolicyBase class
|
||||
# instances with Name added as a ScriptProperty via Add-Member — WPF's
|
||||
# DisplayMemberPath reflection on class instances doesn't reliably see
|
||||
# ScriptProperties, so those rows render blank. Projecting normalizes both.
|
||||
#
|
||||
# Dedup by Id while projecting. The dependency cache can contain BOTH the
|
||||
# synthetic Default and the real Default scope tag from Graph (both have
|
||||
# Id "0"); without dedup, "Default" rendered twice and a single Assigned
|
||||
# move could leave a duplicate stuck in the wrong list.
|
||||
$script:_detailsAllScopeTags = @()
|
||||
$seenIds = [System.Collections.Generic.HashSet[string]]::new()
|
||||
foreach($tag in $allScopeTags) {
|
||||
$idStr = [string]$tag.Id
|
||||
if(-not $seenIds.Add($idStr)) { continue }
|
||||
$script:_detailsAllScopeTags += [PSCustomObject]@{ Id = $idStr; Name = [string]$tag.Name }
|
||||
}
|
||||
$script:_detailsAllScopeTags = @($script:_detailsAllScopeTags | Sort-Object Name)
|
||||
|
||||
# Single source of truth for which tags are currently assigned. Both
|
||||
# ObservableCollections are derived from this set + the full list, so
|
||||
# mutual exclusion is structural — Available is always "everything that
|
||||
# isn't in the assigned set", no chance of duplicate or drift bugs.
|
||||
$script:_detailsAssignedIds = [System.Collections.Generic.HashSet[string]]::new()
|
||||
foreach($id in $assignedIdSet) { [void]$script:_detailsAssignedIds.Add($id) }
|
||||
$script:_detailsOriginalAssignedIds = [System.Collections.Generic.HashSet[string]]::new($script:_detailsAssignedIds)
|
||||
|
||||
$script:colAvailableScopeTags = [System.Collections.ObjectModel.ObservableCollection[object]]::new()
|
||||
$script:colAssignedScopeTags = [System.Collections.ObjectModel.ObservableCollection[object]]::new()
|
||||
|
||||
$script:_syncDetailsScopeTagLists = {
|
||||
$script:colAvailableScopeTags.Clear()
|
||||
$script:colAssignedScopeTags.Clear()
|
||||
foreach($tag in $script:_detailsAllScopeTags) {
|
||||
if($script:_detailsAssignedIds.Contains($tag.Id)) {
|
||||
[void]$script:colAssignedScopeTags.Add($tag)
|
||||
} else {
|
||||
[void]$script:colAvailableScopeTags.Add($tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
& $script:_syncDetailsScopeTagLists
|
||||
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lstAvailableScopeTags", "ItemsSource", $script:colAvailableScopeTags)
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lstAssignedScopeTags", "ItemsSource", $script:colAssignedScopeTags)
|
||||
|
||||
# Click handlers reference only $script:-scoped state so they keep access
|
||||
# to module-private helpers (Get-XamlProperty etc.). Move = mutate the
|
||||
# canonical assigned-ids set, then re-derive both lists.
|
||||
$script:_moveScopeTags = {
|
||||
param([string]$Action, [string[]]$Ids)
|
||||
if($Ids.Count -eq 0) { return }
|
||||
if($Action -eq "assign") {
|
||||
foreach($id in $Ids) { [void]$script:_detailsAssignedIds.Add([string]$id) }
|
||||
} else {
|
||||
foreach($id in $Ids) { [void]$script:_detailsAssignedIds.Remove([string]$id) }
|
||||
}
|
||||
& $script:_syncDetailsScopeTagLists
|
||||
}
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnScopeTagAssign", "Add_Click", {
|
||||
try {
|
||||
$lb = $script:detailsForm.FindName("lstAvailableScopeTags")
|
||||
if(-not $lb -or $lb.SelectedItems.Count -eq 0) {
|
||||
Write-Status "Select one or more scope tag(s) in the Available list first"
|
||||
return
|
||||
}
|
||||
$ids = @($lb.SelectedItems | ForEach-Object { [string]$_.Id })
|
||||
& $script:_moveScopeTags "assign" $ids
|
||||
}
|
||||
catch { Write-LogError "Scope-tag assign handler failed" $_.Exception }
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnScopeTagUnassign", "Add_Click", {
|
||||
try {
|
||||
$lb = $script:detailsForm.FindName("lstAssignedScopeTags")
|
||||
if(-not $lb -or $lb.SelectedItems.Count -eq 0) {
|
||||
Write-Status "Select one or more scope tag(s) in the Assigned list first"
|
||||
return
|
||||
}
|
||||
$ids = @($lb.SelectedItems | ForEach-Object { [string]$_.Id })
|
||||
& $script:_moveScopeTags "unassign" $ids
|
||||
}
|
||||
catch { Write-LogError "Scope-tag unassign handler failed" $_.Exception }
|
||||
})
|
||||
|
||||
# Double-click toggles. Lets the user assign/unassign without aiming for
|
||||
# the arrow buttons, which is the more common interaction pattern.
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "lstAvailableScopeTags", "Add_MouseDoubleClick", {
|
||||
try {
|
||||
$ids = @(@($script:UIProvider.GetXamlProperty($script:detailsForm, "lstAvailableScopeTags", "SelectedItems")) | ForEach-Object { [string]$_.Id })
|
||||
& $script:_moveScopeTags "assign" $ids
|
||||
}
|
||||
catch { Write-LogError "Scope-tag double-click (available) failed" $_.Exception }
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "lstAssignedScopeTags", "Add_MouseDoubleClick", {
|
||||
try {
|
||||
$ids = @(@($script:UIProvider.GetXamlProperty($script:detailsForm, "lstAssignedScopeTags", "SelectedItems")) | ForEach-Object { [string]$_.Id })
|
||||
& $script:_moveScopeTags "unassign" $ids
|
||||
}
|
||||
catch { Write-LogError "Scope-tag double-click (assigned) failed" $_.Exception }
|
||||
})
|
||||
}
|
||||
|
||||
$script:_detailsInitialSettingsSignature = Get-DetailsSettingsSignature -Form $script:detailsForm
|
||||
|
||||
# ToDo: Disable description if property does not exist
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnObjectSettingsSave", "Add_Click", {
|
||||
$selectedItem = $script:detailsForm.Tag
|
||||
if(-not $selectedItem) { return }
|
||||
|
||||
# Name the object and say what changed - computed against the
|
||||
# open-time snapshots so the dialog is meaningful.
|
||||
$originalName = if($script:_detailsOriginalName) { $script:_detailsOriginalName } else { [string]$selectedItem.Name }
|
||||
$newName = [string]($script:UIProvider.GetXamlProperty($script:detailsForm, "txtObjectName", "Text"))
|
||||
$newDesc = [string]($script:UIProvider.GetXamlProperty($script:detailsForm, "txtObjectDescription", "Text"))
|
||||
$changes = @()
|
||||
if($newName -and $newName -cne $originalName) { $changes += "New name: '$newName'" }
|
||||
if($newDesc -cne [string]$script:_detailsOriginalDesc) { $changes += "Description updated" }
|
||||
if($null -ne $script:_detailsAssignedIds -and $null -ne $script:_detailsOriginalAssignedIds)
|
||||
{
|
||||
$added = @($script:_detailsAssignedIds | Where-Object { -not $script:_detailsOriginalAssignedIds.Contains($_) }).Count
|
||||
$removed = @($script:_detailsOriginalAssignedIds | Where-Object { -not $script:_detailsAssignedIds.Contains($_) }).Count
|
||||
if($added -or $removed) { $changes += "Scope tags: $added added, $removed removed" }
|
||||
}
|
||||
$confirmMsg = "Are you sure you want to update '$originalName'?"
|
||||
if($changes.Count) { $confirmMsg += "`n`n" + ($changes -join "`n") }
|
||||
|
||||
if(($script:UIProvider.ShowMessageBox($confirmMsg, "Update object information?", "YesNo", "Warning")) -ne "Yes")
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
Write-Status "Update object information for $($selectedItem.Name)"
|
||||
$nameValue = ($script:UIProvider.GetXamlProperty($script:detailsForm, "txtObjectName", "Text"))
|
||||
if(-not $nameValue)
|
||||
{
|
||||
$script:UIProvider.ShowMessageBox("Name property must not be empty!", "Error", "OK", "Error")
|
||||
return
|
||||
}
|
||||
# Save settings here...
|
||||
$nameProp = (?? $selectedItem.PolicyType.NameProperty "displayName")
|
||||
$idProp = (?? $selectedItem.PolicyType.IDProperty "id")
|
||||
|
||||
$updateHT = @{}
|
||||
$updateHT.Add($idProp, $selectedItem.ID)
|
||||
$updateHT.Add($nameProp, $nameValue)
|
||||
|
||||
if(($selectedItem.Object."@odata.type"))
|
||||
{
|
||||
$updateHT.Add("@odata.type", ($selectedItem.JsonObject."@odata.type"))
|
||||
}
|
||||
|
||||
if(($script:UIProvider.GetXamlProperty($script:detailsForm, "txtObjectDescription", "IsEnabled")) -eq $true)
|
||||
{
|
||||
$updateHT.Add("description", ($script:UIProvider.GetXamlProperty($script:detailsForm, "txtObjectDescription", "Text")))
|
||||
if($null -eq $updateHT["description"] -and $selectedItem.Description)
|
||||
{
|
||||
# If description is null, remove it
|
||||
$updateHT["description"] = ""
|
||||
}
|
||||
elseif($null -eq $updateHT["description"])
|
||||
{
|
||||
$updateHT.Remove("description")
|
||||
}
|
||||
}
|
||||
|
||||
# Scope tags: write the assigned-list IDs back under the policy's
|
||||
# ScopeTagProperty (typically roleScopeTagIds). Read from the canonical
|
||||
# $script:_detailsAssignedIds set (single source of truth) — the
|
||||
# ObservableCollection is just a UI derivative.
|
||||
if($script:_scopeTagPropName -and $null -ne $script:_detailsAssignedIds) {
|
||||
$assignedIds = @($script:_detailsAssignedIds)
|
||||
|
||||
# Soft guard: Intune treats roleScopeTagIds as containing "0" (Default)
|
||||
# by default. A non-empty list that omits Default is unusual and often
|
||||
# rejected by Graph; warn before committing rather than letting the
|
||||
# PATCH 400 silently. Empty list isn't flagged — that reads as a
|
||||
# deliberate reset.
|
||||
if($assignedIds.Count -gt 0 -and $assignedIds -notcontains "0") {
|
||||
$proceed = $script:UIProvider.ShowMessageBox(
|
||||
"The 'Default' scope tag (Id 0) is not in the assigned list.`n`nIntune usually requires Default to be present and the API call may fail.`n`nContinue anyway?",
|
||||
"Default scope tag missing", "YesNo", "Warning")
|
||||
if($proceed -ne "Yes") {
|
||||
Write-Status ""
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
$updateHT.Add($script:_scopeTagPropName, $assignedIds)
|
||||
}
|
||||
|
||||
$updateObj = [PSCustomObject]$updateHT
|
||||
|
||||
$api = "$($selectedItem.PolicyType.API)/$($selectedItem.ID)"
|
||||
|
||||
$json = $updateObj | ConvertTo-Json -Depth 20
|
||||
|
||||
$ret = Invoke-MSGraphAPI $api -HttpMethod "PATCH" -Content $json -FullResponseObject -TokenId $selectedItem._TokenId
|
||||
Write-Status ""
|
||||
if($ret.Success -eq $false)
|
||||
{
|
||||
Write-log "Failed to update object information updated for $($selectedItem.Name). Error: $($ret.StatusCode) - $($ret.StatusDescription)"
|
||||
$script:UIProvider.ShowMessageBox("Object information could not be verified!`n`nCheck the log file", "Update warning", "OK", "Warning")
|
||||
}
|
||||
else {
|
||||
Write-log "Object information updated for $($selectedItem.Name)"
|
||||
|
||||
# Refresh the in-memory object so the parent grid's "ScopeTags" column
|
||||
# reflects what we just sent. Clearing _ScopeTags forces the lazy
|
||||
# getter to re-resolve from the dependency cache next access; updating
|
||||
# Object.<prop> keeps subsequent reads (and a re-open of this dialog)
|
||||
# in sync without a round-trip. Items.Refresh() then asks WPF to
|
||||
# re-read every bound cell — without it, the DataGrid keeps showing
|
||||
# the pre-Save snapshot because PSCustomObject doesn't notify.
|
||||
if($script:_scopeTagPropName -and $null -ne $script:_detailsAssignedIds) {
|
||||
$assignedIds = @($script:_detailsAssignedIds)
|
||||
try {
|
||||
$selectedItem.Object.$script:_scopeTagPropName = $assignedIds
|
||||
} catch { Write-LogDebug "Failed to update in-memory ScopeTags array: $($_.Exception.Message)" }
|
||||
$selectedItem._ScopeTags = $null
|
||||
$selectedItem._ScopeTagsString = $null
|
||||
}
|
||||
|
||||
# Name / description may also have changed in this Save — refresh once
|
||||
# at the end so every cell of the affected row re-reads, not just the
|
||||
# ScopeTags column.
|
||||
try { $script:dgIntuneManagerObjects.Items.Refresh() }
|
||||
catch { Write-LogDebug "DataGrid refresh failed: $($_.Exception.Message)" }
|
||||
|
||||
$script:_detailsInitialSettingsSignature = Get-DetailsSettingsSignature -Form $script:detailsForm
|
||||
}
|
||||
})
|
||||
|
||||
#Columns tab
|
||||
$skipBasicProperties = @("JsonObject", "Object", "JsonString", "TenantId", "TokenId", "IsSelected")
|
||||
$arrBasicProperties = @()
|
||||
foreach($prop in ($selectedItem.PSObject.Properties | Where-Object Name -notin $skipBasicProperties))
|
||||
{
|
||||
$arrBasicProperties += ([PSCustomObject]@{ Name=$prop.Name;Value=$prop;Source="Basic" })
|
||||
}
|
||||
$arrBasicProperties = $arrBasicProperties | Sort-Object -Property Name
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lstBasicProperties", "ItemsSource", $arrBasicProperties)
|
||||
|
||||
$arrObjectProperties = @()
|
||||
$skipObjectProperties = @("@odata.editLink")
|
||||
foreach($prop in ($selectedItem.Object.PSObject.Properties | Where-Object { $_.Name -notin $skipObjectProperties -and $_.Name -notlike "*?@odata.*" }))
|
||||
{
|
||||
$arrObjectProperties += ([PSCustomObject]@{ Name=$prop.Name;Value=$prop;Source="Object" })
|
||||
}
|
||||
$arrObjectProperties = $arrObjectProperties | Sort-Object -Property Name
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lstObjectProperties", "ItemsSource", $arrObjectProperties)
|
||||
|
||||
$script:colObjectProperties = [System.Collections.ObjectModel.ObservableCollection[object]]::new()
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lstObjectColumns", "ItemsSource", $script:colObjectProperties)
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnObjectColumnsReset", "Add_Click", {
|
||||
<#
|
||||
if(([System.Windows.MessageBox]::Show("Are you sure you want to reset columns to default?", "Reset Columns?", "YesNo", "Warning")) -ne "Yes")
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
$selectedType = Get-SelectedObjectTypeString
|
||||
Remove-SettingStoreValue "IntuneManager\ObjectColumns\$selectedType" "$($script:IntuneManagerSelectedObject.Id)"
|
||||
#>
|
||||
|
||||
$script:colObjectProperties.Clear()
|
||||
Show-ObjectDefaultColumnsSettings -Reset
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "lstObjectColumns", "Add_SelectionChanged", {
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "grdObjectColumns", "DataContext", ($script:UIProvider.GetXamlProperty($script:detailsForm, "lstObjectColumns", "SelectedItem")))
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnBasicColumnsAdd", "Add_Click", {
|
||||
$selectedItem = $script:UIProvider.GetXamlProperty($script:detailsForm, "lstBasicProperties", "SelectedItem")
|
||||
if($selectedItem) {
|
||||
$script:colObjectProperties.Add(([ObjectColumnInfo]::new($selectedItem.Name, "")))
|
||||
}
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnObjectColumnsAdd", "Add_Click", {
|
||||
$selectedItem = $script:UIProvider.GetXamlProperty($script:detailsForm, "lstObjectProperties", "SelectedItem")
|
||||
if($selectedItem) {
|
||||
$script:colObjectProperties.Add(([ObjectColumnInfo]::new("Object." + $selectedItem.Name, "")))
|
||||
}
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnObjectColumnsMoveUp", "Add_Click", {
|
||||
$selectedIndex = $script:UIProvider.GetXamlProperty($script:detailsForm, "lstObjectColumns", "SelectedIndex")
|
||||
if($selectedIndex -gt 0)
|
||||
{
|
||||
$tmpObj = $script:colObjectProperties[$selectedIndex]
|
||||
$script:colObjectProperties.RemoveAt($selectedIndex)
|
||||
$tmpObj = $script:colObjectProperties.Insert(($selectedIndex-1),$tmpObj)
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lstObjectColumns", "SelectedIndex", ($selectedIndex-1))
|
||||
}
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnObjectColumnsMoveDown", "Add_Click", {
|
||||
$selectedIndex = $script:UIProvider.GetXamlProperty($script:detailsForm, "lstObjectColumns", "SelectedIndex")
|
||||
if($selectedIndex -ge 0 -and $selectedIndex -lt ($script:colObjectProperties.Count-1)) {
|
||||
$tmpObj = $script:colObjectProperties[$selectedIndex]
|
||||
$script:colObjectProperties.RemoveAt($selectedIndex)
|
||||
$tmpObj = $script:colObjectProperties.Insert(($selectedIndex+1),$tmpObj)
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lstObjectColumns", "SelectedIndex", ($selectedIndex+1))
|
||||
}
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnObjectColumnsDelete", "Add_Click", {
|
||||
$selectedIndex = $script:UIProvider.GetXamlProperty($script:detailsForm, "lstObjectColumns", "SelectedIndex")
|
||||
if($selectedIndex -ge 0) {
|
||||
if(($script:UIProvider.ShowMessageBox("Are you sure you want to remove selected column?", "Remove Columns?", "YesNo", "Warning")) -ne "Yes")
|
||||
{
|
||||
return
|
||||
}
|
||||
$script:colObjectProperties.RemoveAt($selectedIndex)
|
||||
}
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnObjectColumnsClear", "Add_Click", {
|
||||
if(($script:UIProvider.ShowMessageBox("Are you sure you want to clear custom column settings?", "Clear Custom Columns?", "YesNo", "Warning")) -ne "Yes") {
|
||||
return
|
||||
}
|
||||
$script:colObjectProperties.Clear()
|
||||
})
|
||||
|
||||
$script:UIProvider.AddXamlEvent($script:detailsForm, "btnObjectColumnsSave", "Add_Click", {
|
||||
$selectedType = Get-SelectedObjectTypeString
|
||||
if(-not $selectedType) { return }
|
||||
|
||||
if(($script:UIProvider.ShowMessageBox("Are you sure you want to save custom column settings?", "Save Custom Columns?", "YesNo", "Warning")) -ne "Yes")
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
if($script:colObjectProperties.Count -gt 0)
|
||||
{
|
||||
$arrCols = @()
|
||||
if(($script:UIProvider.GetXamlProperty($script:detailsForm, "chkObjectColumnOverride", "IsChecked")) -eq $true)
|
||||
{
|
||||
$arrCols += "0"
|
||||
}
|
||||
|
||||
foreach($colProp in $script:colObjectProperties)
|
||||
{
|
||||
$tmp = $colProp.Property
|
||||
if($colProp.Header -and $colProp.Header -cne $colProp.Property)
|
||||
{
|
||||
$tmp = "$($tmp)=$($colProp.Header)"
|
||||
}
|
||||
|
||||
$arrCols += $tmp
|
||||
}
|
||||
$strCols = $arrCols -join ","
|
||||
|
||||
Save-SettingStoreValue "IntuneManager\ObjectColumns\$selectedType" "$($script:IntuneManagerSelectedObject.Id)" $strCols
|
||||
}
|
||||
else
|
||||
{
|
||||
$strCols = $null
|
||||
Remove-SettingStoreValue "IntuneManager\ObjectColumns\$selectedType" "$($script:IntuneManagerSelectedObject.Id)"
|
||||
}
|
||||
|
||||
Show-ObjectDefaultColumnsSettings
|
||||
$script:_detailsInitialColumnsSignature = Get-DetailsColumnsSignature -Form $script:detailsForm
|
||||
})
|
||||
|
||||
|
||||
Show-ObjectDefaultColumnsSettings
|
||||
$script:_detailsInitialColumnsSignature = Get-DetailsColumnsSignature -Form $script:detailsForm
|
||||
|
||||
# Show dialog
|
||||
$script:UIProvider.ShowModalForm($FormTitle, $detailsForm)
|
||||
}
|
||||
|
||||
function Show-ObjectDefaultColumnsSettings
|
||||
{
|
||||
param([switch]$Reset)
|
||||
|
||||
if($Reset -ne $true) {
|
||||
$selectedType = Get-SelectedObjectTypeString
|
||||
if(-not $selectedType) { return }
|
||||
|
||||
$strColSettings = Get-SettingStoreValue "IntuneManager\ObjectColumns\$selectedType" "$($script:IntuneManagerSelectedObject.Id)"
|
||||
}
|
||||
else {
|
||||
$strColSettings = ""
|
||||
}
|
||||
$script:colObjectProperties.Clear()
|
||||
|
||||
$defaultColumns = $script:IntuneManagerSelectedObject.ViewProperties
|
||||
|
||||
if($strColSettings)
|
||||
{
|
||||
$arrColSettings += $strColSettings -split ",|;"
|
||||
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "chkObjectColumnOverride", "IsChecked", ($arrColSettings.Count -gt 0 -and $arrColSettings[0] -eq "0"))
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lblObjectColumnsConfig", "Text", $strColSettings)
|
||||
|
||||
$start = 0
|
||||
if($arrColSettings.Count -gt 0 -and ($arrColSettings[0] -eq "0" -or $arrColSettings[0] -eq "1"))
|
||||
{
|
||||
$start++
|
||||
}
|
||||
|
||||
$colArr = @()
|
||||
|
||||
for($i = $start;$i -lt $arrColSettings.Count;$i++)
|
||||
{
|
||||
$colProp,$colHeader= $arrColSettings[$i].Split("=")
|
||||
if(-not $colHeader)
|
||||
{
|
||||
$colHeader = $colProp
|
||||
}
|
||||
$script:colObjectProperties.Add([ObjectColumnInfo]::new($colProp,$colHeader))
|
||||
$colArr += $colProp
|
||||
}
|
||||
|
||||
if(($arrColSettings.Count -eq 0 -or $arrColSettings[0] -ne "0"))
|
||||
{
|
||||
$tmpArr = $defaultColumns
|
||||
$tmpArr += $colArr
|
||||
|
||||
$colArr = $tmpArr
|
||||
}
|
||||
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lblObjectColumnsConfig", "Text", ("$(($colArr-join ','))"))
|
||||
}
|
||||
else
|
||||
{
|
||||
$script:UIProvider.SetXamlProperty($script:detailsForm, "lblObjectColumnsConfig", "Text", "$(($defaultColumns -join ',')) (Default)")
|
||||
}
|
||||
}
|
||||
|
||||
# Match-resolution helpers (Get-IntuneImportPolicyReferenceTokens,
|
||||
# Normalize-IntuneImportPolicyName, New-IntuneImportMatchResult,
|
||||
# Resolve-IntuneImportUpdateTarget) moved to Internal/IntuneManager.ps1
|
||||
# so the Avalonia tree can share them.
|
||||
Reference in New Issue
Block a user