IntuneManagement 4.0.0-beta1

This commit is contained in:
Mikael Karlsson
2026-09-23 19:13:09 +10:00
commit 7869619510
892 changed files with 577109 additions and 0 deletions
View File
+17
View File
@@ -0,0 +1,17 @@
#ImportOrder 6
# CLR-typed row for the ADMX listBox element editor (Setting Properties
# dialog). Was previously a `ListValue` class defined via Invoke-Expression
# inside Set-ADMXElementsPanel - a type created that way is not resolvable
# from the module's event handlers at click time, which killed the list
# editor's Add button. A real class file loads with the module and is
# visible everywhere (and, being CLR-typed, binds in the DataGrid - see
# [[avalonia-binding-needs-clr-types]]).
class ADMXListValueItem
{
[string]$Key = ''
[string]$Value = ''
ADMXListValueItem() {}
}
+15
View File
@@ -0,0 +1,15 @@
#ImportOrder 4
# CLR-typed shape for rows in the About dialog's loaded-modules ListBox.
# See [[avalonia-binding-needs-clr-types]] — Avalonia's binder does not
# special-case PSObject NoteProperties, so DataTemplate items must be real
# CLR types with real properties.
class AboutModuleEntry
{
[string]$Name
[string]$Version
[string]$Type
AboutModuleEntry() {}
}
+218
View File
@@ -0,0 +1,218 @@
#ImportOrder 60
#
# Avalonia concrete subclass of UIProvider. Each method captures the
# corresponding free function from CoreUIAvalonia.ps1 (and friends) at construction
# time via ${function:X}, then invokes the captured scriptblock with `&`.
# This is defensive: PS native class methods *may* see module-scoped free
# functions, but the documented workaround for ScriptMethod scriptblocks
# (see [[scriptmethod-loses-module-scope]]) is the capture pattern. Until a
# smoke test confirms direct calls work, we capture explicitly.
#
# Folder picking is exposed through the provider even though the implementation
# delegates to Avalonia's StorageProvider host wrapper.
class AvaloniaUIProvider : UIProvider {
#region Modal / dialog
[void] ShowMessageBox([string]$Text) {
$f = ${function:Show-MessageBox}
& $f $Text | Out-Null
}
[void] ShowMessageBox([string]$Text, [string]$Caption) {
$f = ${function:Show-MessageBox}
& $f $Text $Caption | Out-Null
}
[object] ShowMessageBox([string]$Text, [string]$Caption, [string]$Button, [string]$Icon) {
$f = ${function:Show-MessageBox}
return (& $f $Text $Caption $Button $Icon)
}
[void] ShowModalForm([string]$FormTitle, [object]$FormObject) {
$f = ${function:Show-ModalForm}
& $f $FormTitle $FormObject
}
[void] ShowModalForm([string]$FormTitle, [object]$FormObject, [bool]$HideButtons) {
$f = ${function:Show-ModalForm}
if ($HideButtons) { & $f $FormTitle $FormObject -HideButtons }
else { & $f $FormTitle $FormObject }
}
[void] ShowModalObject() {
$f = ${function:Show-ModalObject}
& $f
}
[void] ShowModalObject([object]$Obj) {
$f = ${function:Show-ModalObject}
& $f $Obj
}
[void] CloseTopModalObject() {
$f = ${function:Close-TopModalObject}
& $f
}
[string] ShowInputDialog([string]$FormTitle, [string]$FormText, [string]$DefaultValue) {
$f = ${function:Show-InputDialog}
return (& $f $FormTitle $FormText $DefaultValue)
}
[void] ShowAboutDialog() {
$f = ${function:Show-AboutDialog}
& $f
}
[bool] RequestUIConfirmation([string]$Message, [string]$Caption) {
$f = ${function:Request-UIConfirmation}
return [bool](& $f $Message $Caption)
}
[void] ShowPopup([object]$Popup) {
$f = ${function:Show-Popup}
& $f $Popup
}
[void] HidePopup() {
$f = ${function:Hide-Popup}
& $f
}
#endregion
#region File / folder pickers
[string] ShowFolderPicker([string]$Description) {
$f = ${function:Get-AvaloniaHost}
$hostType = & $f
return $hostType::OpenFolderPicker($script:Window, $Description)
}
[string] ShowFolderPicker([string]$Path, [string]$Description) {
$f = ${function:Get-AvaloniaHost}
$hostType = & $f
return $hostType::OpenFolderPicker($script:Window, $Description, $Path)
}
#endregion
#region XAML helpers
[object] GetXamlObject([string]$FileName) {
$f = ${function:Get-XamlObject}
return (& $f $FileName)
}
[object] GetXamlObject([string]$FileName, [bool]$AddVariables) {
$f = ${function:Get-XamlObject}
if ($AddVariables) { return (& $f $FileName -AddVariables) }
else { return (& $f $FileName) }
}
[object] GetXamlObject([string]$FileName, [bool]$AddVariables, [bool]$AddStyles) {
$f = ${function:Get-XamlObject}
$h = @{}
if ($AddVariables) { $h.AddVariables = $true }
if ($AddStyles) { $h.AddStyles = $true }
return (& $f $FileName @h)
}
[void] AddXamlEvent([object]$XamlObj, [string]$ControlName, [string]$EventName, [scriptblock]$Handler) {
$f = ${function:Add-XamlEvent}
& $f $XamlObj $ControlName $EventName $Handler
}
[void] AddXamlVariables([object]$XamlObj, [object]$Scope) {
$f = ${function:Add-XamlVariables}
& $f $XamlObj $Scope
}
[void] SetXamlProperty([object]$XamlObj, [string]$ControlName, [string]$PropertyName, [object]$Value) {
$f = ${function:Set-XamlProperty}
& $f $XamlObj $ControlName $PropertyName $Value
}
[object] GetXamlProperty([object]$XamlObj, [string]$ControlName, [string]$PropertyName) {
$f = ${function:Get-XamlProperty}
return (& $f $XamlObj $ControlName $PropertyName)
}
[void] SetControlVisible([object]$XamlObj, [string]$ControlName, [bool]$Visible) {
$this.SetXamlProperty($XamlObj, $ControlName, "IsVisible", $Visible)
}
#endregion
#region Main window / chrome
[void] ShowMainWindow([object]$View) {
$f = ${function:Show-MainWindow}
& $f $View
}
[void] SetMainTitle() {
$f = ${function:Set-MainTitle}
& $f
}
[object] GetMainWindow() {
$f = ${function:Get-MainWindow}
return (& $f)
}
[void] SetAppTheme([string]$ThemeName) {
$f = ${function:Set-AppTheme}
& $f $ThemeName
}
#endregion
#region Threading / status
[void] UpdateUIStatus([string]$Text) {
$f = ${function:Update-UIStatus}
& $f -Text $Text | Out-Null
}
[bool] UpdateUIStatus([System.Collections.IDictionary]$Params) {
$f = ${function:Update-UIStatus}
$h = @{}
foreach ($k in $Params.Keys) { $h[$k] = $Params[$k] }
return [bool](& $f @h)
}
[void] InvokeUIMessagePump() {
$f = ${function:Invoke-UIMessagePump}
& $f
}
[void] SetClipboardText([string]$Text) {
$f = ${function:Get-AvaloniaHost}
$hostType = & $f
$hostType::SetClipboardText($script:Window, $Text)
}
#endregion
#region Auth chrome
[void] ShowAuthenticationInfo() {
$f = ${function:Show-AuthenticationInfo}
& $f
}
[void] SetEnvironmentInfo() {
$f = ${function:Set-EnvironmentInfo}
& $f
}
[void] SetEnvironmentInfo([string]$TenantName) {
$f = ${function:Set-EnvironmentInfo}
& $f $TenantName
}
#endregion
}
+18
View File
@@ -0,0 +1,18 @@
#ImportOrder 10
# CLR row shape for Show-GraphBulkAssignmentsForm's dgBulkAssignObjects
# DataGrid (the left-hand "Objects to update" grid). Mirrors
# [[BulkScopeTagRowItem]] / [[BulkExportRowItem]] — Group/API mode means
# exactly one of ObjectGroup / ObjectType is populated, the other is $null.
# Avalonia's binder ignores PSObject NoteProperties, so PSCustomObject would
# render Selected / Title blank — see [[avalonia-binding-needs-clr-types]].
class BulkAssignRowItem
{
[bool] $Selected = $true
[string] $Title
[object] $ObjectGroup
[object] $ObjectType
BulkAssignRowItem() {}
}
@@ -0,0 +1,32 @@
#ImportOrder 10
# CLR row shape for the dgBulkAssignList DataGrid in Show-GraphBulkAssignmentsForm.
# The WPF original uses PSCustomObject here; Avalonia's binder needs real CLR
# properties (see [[avalonia-binding-needs-clr-types]]).
#
# Set-GraphBulkAssignments accesses these by name (.TargetType, .GroupId,
# .Intent, .Settings as [Hashtable]) so the public command consumes class
# instances the same way it consumes PSCustomObject — confirmed by reading
# Public/Set-GraphBulkAssignments.ps1.
#
# Settings is keyed by Graph settings type name (e.g.
# "win32LobAppAssignmentSettings"). Absent key = no override for that type.
# SettingsDisplay is a derived count string ("(none)" / "N platform(s)") that
# the App settings... dialog refreshes on save.
class BulkAssignmentRowItem
{
[string] $TargetType
[string] $TargetTypeDisplay
[string] $GroupId
[string] $GroupName
[string] $FilterId
[string] $FilterName
[string] $FilterType = "include"
[string] $FilterDisplay = ""
[string] $Intent = "required"
[Hashtable] $Settings = @{}
[string] $SettingsDisplay = "(none)"
BulkAssignmentRowItem() {}
}
@@ -0,0 +1,16 @@
#ImportOrder 10
# CLR row shape for Show-GraphBulkCompareForm's dgObjectsToCompare. Same
# reason as [[BulkExportRowItem]] / [[avalonia-binding-needs-clr-types]] —
# Avalonia's binder ignores PSObject NoteProperties. Default Selected is
# true (matches WPF original — every object type pre-selected for a
# bulk compare run).
class BulkCompareRowItem
{
[bool] $Selected = $true
[string] $Title
[object] $ObjectGroup
BulkCompareRowItem() {}
}
+15
View File
@@ -0,0 +1,15 @@
#ImportOrder 10
# CLR row shape for Show-GraphBulkCopyForm's dgBulkCopyObjects. Same reason
# as [[BulkDeleteRowItem]] / [[avalonia-binding-needs-clr-types]] — Avalonia's
# binder ignores PSObject NoteProperties. Default Selected is true (matches
# the original Bulk Copy where every type was pre-checked).
class BulkCopyRowItem
{
[bool] $Selected = $true
[string] $Title
[object] $ObjectType
BulkCopyRowItem() {}
}
+15
View File
@@ -0,0 +1,15 @@
#ImportOrder 10
# CLR row shape for Show-GraphBulkDeleteForm's dgBulkDeleteObjects. Same
# reason as [[BulkExportRowItem]] / [[avalonia-binding-needs-clr-types]] —
# Avalonia's binder ignores PSObject NoteProperties. Default Selected is
# false (matches WPF; bulk delete is destructive and shouldn't pre-select).
class BulkDeleteRowItem
{
[bool] $Selected = $false
[string] $Title
[object] $ObjectGroup
BulkDeleteRowItem() {}
}
@@ -0,0 +1,19 @@
#ImportOrder 10
# CLR row shape for Show-GraphBulkDocumentationForm's dgObjectsToDocument.
# Same reason as [[BulkExportRowItem]] / [[avalonia-binding-needs-clr-types]] —
# Avalonia's binder ignores PSObject NoteProperties, so Selected and Title
# would render blank if we used PSCustomObject. ObjectGroup / ObjectType
# hold references to the underlying IntuneGroup / IntuneType / policy so the
# click handler can pass the appropriate selection into Start-GraphBulkDocumentation.
class BulkDocumentationRowItem
{
[bool] $Selected = $true
[string] $Title
[object] $ObjectGroup
[object] $ObjectType
[object] $PolicyObject
BulkDocumentationRowItem() {}
}
+17
View File
@@ -0,0 +1,17 @@
#ImportOrder 10
# CLR row shape for Show-GraphBulkExportForm's dgObjectsToExport. WPF used
# [PSCustomObject]@{...} but Avalonia's binder ignores PSObject NoteProperties
# (see [[avalonia-binding-needs-clr-types]]) — Selected and Title would render
# blank. ObjectGroup / ObjectType keep references to the underlying
# IntuneGroup / IntuneType so Get-BulkExportSelectedIds can pull .Id off them.
class BulkExportRowItem
{
[bool] $Selected = $true
[string] $Title
[object] $ObjectGroup
[object] $ObjectType
BulkExportRowItem() {}
}
+17
View File
@@ -0,0 +1,17 @@
#ImportOrder 10
# CLR row shape for Show-GraphBulkImportForm's dgObjectsToImport. Same reason
# as [[BulkExportRowItem]] / [[avalonia-binding-needs-clr-types]] — Avalonia's
# binder ignores PSObject NoteProperties, so Selected and Title would render
# blank if we used [PSCustomObject]@{...} like the WPF original. ObjectGroup
# keeps a reference to the underlying IntuneGroup so the import handler can
# pull .PolicyTypes off it.
class BulkImportRowItem
{
[bool] $Selected = $true
[string] $Title
[object] $ObjectGroup
BulkImportRowItem() {}
}
@@ -0,0 +1,18 @@
#ImportOrder 10
# CLR row shape for Show-GraphBulkScopeTagForm's dgBulkScopeTagObjects.
# Same reasoning as [[BulkExportRowItem]] / [[avalonia-binding-needs-clr-types]] —
# Avalonia's binder ignores PSObject NoteProperties, so Selected / Title would
# render blank if we used PSCustomObject. Mirrors BulkExportRowItem because
# this form also offers Group / API mode (one of ObjectGroup / ObjectType is
# populated, the other is $null).
class BulkScopeTagRowItem
{
[bool] $Selected = $true
[string] $Title
[object] $ObjectGroup
[object] $ObjectType
BulkScopeTagRowItem() {}
}
+21
View File
@@ -0,0 +1,21 @@
#ImportOrder 9
# CLR-typed row shape for the Cached Objects DataGrid (CachedObjects.axaml).
# Update-CachedObjectsView builds these from $script:cacheObjects (a hashtable
# of PSCustomObject in non-UI code) — Avalonia's binder needs CLR types here
# rather than the raw cache entries.
class CacheRowItem
{
[string] $Name
[object] $Tags
[string] $TagsText
[object] $Value
[string] $ValueText
[bool] $Persistent
[datetime] $TimeOut
[long] $Size
[string] $SizeText
CacheRowItem() {}
}
@@ -0,0 +1,27 @@
#ImportOrder 11
# CLR-typed row shape for the Compare DataGrid. Compare-PolicyObjects
# returns PSCustomObject rows, which Avalonia's reflection-based binder
# can't traverse (see [[avalonia-binding-needs-clr-types]]). Project each
# into one of these before assigning DataGrid.ItemsSource.
#
# Match is nullable bool (e.g. ignored core properties produce $null).
# Slice 3d3 uses it to drive row foreground color.
#
# MatchGlyph is the precomputed tri-state glyph the "Match" DataGrid column
# binds to (checkmark / ballot-X / em-dash), mirroring the WPF CompareForm.xaml
# DataTrigger chain. Avalonia has no WPF-style per-value DataTrigger, so the
# glyph is computed at projection time instead of in XAML.
class CompareResultRowItem
{
[string] $PropertyName
[string] $Category
[string] $SubCategory
[string] $Object1Value
[string] $Object2Value
[object] $Match
[string] $MatchGlyph
CompareResultRowItem() {}
}
@@ -0,0 +1,19 @@
#ImportOrder 10
# CLR-typed shape for items inside the Copy dialog's scope-tag ListBox
# controls. Get-GraphPolicies / Get-CacheObject return PSCustomObject tags
# whose NoteProperties don't bind through Avalonia's reflection-based
# ItemTemplate (see [[avalonia-binding-needs-clr-types]]). The Copy dialog
# projects each PSCustomObject tag into one of these before assigning to
# the ObservableCollection bound to the ListBox.
class CopyDialogScopeTagItem
{
[string] $Id
[string] $Name
CopyDialogScopeTagItem() {}
# Backstop only - the control selects the field via DisplayMemberBinding.
[string] ToString() { return [string]$this.Name }
}
@@ -0,0 +1,20 @@
#ImportOrder 10
# CLR-typed item shape for the Copy dialog's destination-tenant ComboBox.
# Get-TokenInfo returns PSObjects whose NoteProperties don't bind through
# Avalonia's reflection-based ItemTemplate (see [[avalonia-binding-needs-clr-types]]).
# Project the fields we actually need into this class; the original token
# stays on .Source for the Copy call.
class CopyDialogTenantItem
{
[string] $TenantNameEx
[int] $Id
[bool] $IsDefault
[object] $Source
CopyDialogTenantItem() {}
# Backstop only - the control selects the field via DisplayMemberBinding.
[string] ToString() { return [string]$this.TenantNameEx }
}
@@ -0,0 +1,138 @@
#ImportOrder 5
# Avalonia counterparts of the three debug ViewObjects in
# UI/WPF/Classes/CoreUIBaseClassesWPF.ps1 (LogViewObject, CachedObjectsViewObject,
# GraphCallsViewObject). Get-SubClasses("ViewObjectBase") in Show-MainWindow
# auto-discovers these and Add-ViewObject registers each one — same flow as
# the existing PreviewViewObject sandbox view.
#
# All three set _HideMenu=$true: per Show-View, that hides the in-view item
# menu (these panels host their own DataGrid rather than a row of view items).
# AddToMenu stays at the default $true so they appear in the left nav under
# the Views section.
class LogViewObject : ViewObjectBase
{
LogViewObject() : base()
{
$this.Init()
}
Hidden Init()
{
$this._ID = "CoreLog"
$this._Title = "Log"
$this._Description = "View log items"
$this._HideMenu = $true
}
[Object] GetViewPanel()
{
if ($null -eq $this._ViewPanel) {
$this._ViewPanel = Get-LogViewPanel
}
return $this._ViewPanel
}
[Object[]] GetViewItems() { return $null }
Authenticate() {}
[Object[]] OnItemChanged($SelectedItem) { return $null }
OnDeactivating($NewActiveView) {}
OnActivating($PreviousActiveView) {}
OnActivated()
{
# Re-bind log items each activation so entries logged while the view
# was inactive show up. ItemsSource is reassigned (not appended) so
# the DataGrid picks up the latest snapshot.
if ($this._ViewPanel) {
Update-LogView -ViewPanel $this._ViewPanel
}
}
}
class CachedObjectsViewObject : ViewObjectBase
{
CachedObjectsViewObject() : base()
{
$this.Init()
}
Hidden Init()
{
$this._ID = "CoreCachedObjects"
$this._Title = "Cached Objects"
$this._Description = "View cached items"
$this._HideMenu = $true
}
[Object] GetViewPanel()
{
if ($null -eq $this._ViewPanel) {
$this._ViewPanel = Get-CachedObjectsViewPanel
}
return $this._ViewPanel
}
[Object[]] GetViewItems() { return $null }
Authenticate() {}
[Object[]] OnItemChanged($SelectedItem) { return $null }
OnDeactivating($NewActiveView) {}
OnActivating($PreviousActiveView) {}
OnActivated()
{
if ($this._ViewPanel) {
Update-CachedObjectsView -ViewPanel $this._ViewPanel
}
}
}
class GraphCallsViewObject : ViewObjectBase
{
GraphCallsViewObject() : base()
{
$this.Init()
}
Hidden Init()
{
$this._ID = "GraphCalls"
$this._Title = "Graph Calls"
$this._Description = "View all calls to Microsoft Graph"
$this._HideMenu = $true
}
[Object] GetViewPanel()
{
if ($null -eq $this._ViewPanel) {
$this._ViewPanel = Get-GraphCallsViewPanel
}
return $this._ViewPanel
}
[Object[]] GetViewItems() { return $null }
Authenticate() {}
[Object[]] OnItemChanged($SelectedItem) { return $null }
OnDeactivating($NewActiveView) {}
OnActivating($PreviousActiveView) {}
OnActivated()
{
if ($this._ViewPanel) {
Update-GraphCallsView -ViewPanel $this._ViewPanel
}
}
}
@@ -0,0 +1,22 @@
#ImportOrder 10
# CLR row shape for the Object Details "Columns" tab — used by both
# lstBasicProperties and lstObjectProperties. WPF sets DisplayMemberPath="Name"
# on a [PSCustomObject]@{ Name; Value; Source }, but Avalonia has no
# DisplayMemberPath and its binder doesn't reflect into PSCustomObject
# NoteProperties (see [[avalonia-binding-needs-clr-types]]) — so we project
# into this typed shape and wire an inline ItemTemplate.
class DetailsPropertyItem
{
[string] $Name
[object] $Value
[string] $Source
DetailsPropertyItem() {}
[string] ToString()
{
return [string]$this.Name
}
}
@@ -0,0 +1,21 @@
#ImportOrder 10
# CLR-typed row for the Permissions dialog's DataGrid
# (Extensions/EffectivePermissionsUIAvalonia.ps1). Get-GraphEffectivePermissions
# returns PSCustomObjects, which the Avalonia binder renders blank, so the
# dialog converts each row to an instance of this class.
# Property declaration order is the column order (AutoGenerateColumns).
class EffectivePermissionRowItem
{
[string] $Type
[string] $Category
[string] $Required
[string] $Token
[string] $IntuneRole
[string] $Effective
[string] $Result
[string] $Reason
EffectivePermissionRowItem() {}
}
@@ -0,0 +1,21 @@
#ImportOrder 8
# CLR-typed row shape for the nested batch-request DataGrid in
# GraphCallsPanel.axaml. Lives one level below GraphCallRowItem and is bound
# via {Binding SelectedItem.BatchRequests, ElementName=dgGraphCalls}.
#
# ImportOrder 8 (loads before GraphCallRowItem at 9) so the
# List[GraphBatchRequestRowItem] type reference on GraphCallRowItem resolves.
class GraphBatchRequestRowItem
{
[string] $Id
[double] $KB
[int] $ObjectCount
[int] $PageCount
[string] $StatusCode
[string] $Method
[string] $URL
GraphBatchRequestRowItem() {}
}
+29
View File
@@ -0,0 +1,29 @@
#ImportOrder 9
# CLR-typed row shape for the Graph Calls DataGrid (GraphCallsPanel.axaml).
# Internal/MSGraph.ps1 stores entries in $script:AllGraphCalls as
# PSCustomObject; Update-GraphCallsView projects them into these so the
# Avalonia binder can resolve column paths and the BatchRequests sub-grid.
#
# BatchRequests is exposed as List[GraphBatchRequestRowItem] so the nested
# DataGrid (bound via SelectedItem.BatchRequests) gets a typed enumerable.
class GraphCallRowItem
{
[string] $Provider
[datetime] $Time
[double] $Duration
[double] $KB
[int] $ObjectCount
[int] $PageCount
[string] $StatusCode
[string] $BatchErrorSummary
[string] $Method
[string] $URL
[bool] $IsBatch
[System.Collections.Generic.List[GraphBatchRequestRowItem]] $BatchRequests
GraphCallRowItem() {
$this.BatchRequests = [System.Collections.Generic.List[GraphBatchRequestRowItem]]::new()
}
}
@@ -0,0 +1,28 @@
#ImportOrder 8
# CLR-typed row shape for the Intune Assignments DataGrid. Get-IntuneAssignmentRow
# in Internal/IntuneAssignments.ps1 returns [PSCustomObject]; Avalonia's binder
# can't traverse those (see [[avalonia-binding-needs-clr-types]]), so the
# Avalonia tool converts each PSCustomObject into one of these before assigning
# ItemsSource. Properties mirror what the DataGrid columns + filter + CSV
# export read.
class IntuneAssignmentRowItem
{
[string] $Name
[string] $Type
[int] $AssignmentCount
[bool] $HasFilters
[string] $IncludedString
[string] $ExcludedString
[string] $IncludedFilterString
[string] $ExcludedFilterString
[string] $Id
[object] $Object
[object[]] $Included
[object[]] $Excluded
[object[]] $IncludedFilters
[object[]] $ExcludedFilters
IntuneAssignmentRowItem() {}
}
@@ -0,0 +1,20 @@
#ImportOrder 9
# CLR-typed row shape for the Intune Filter Usage DataGrid. Get-IntuneFilterUsageData
# returns [PSCustomObject] rows; Avalonia binder can't traverse those, so
# Start-IntuneFilterUsageLoad converts each into one of these before assigning
# DataGrid.ItemsSource.
class IntuneFilterUsageRowItem
{
[string] $FilterName
[string] $Platform
[string] $FilterType
[string] $PolicyName
[string] $PayloadType
[string] $Mode
[string] $GroupId
[string] $GroupName
IntuneFilterUsageRowItem() {}
}
@@ -0,0 +1,27 @@
#ImportOrder 10
# CLR-typed row shape for the Import dialog's dgObjectsToImport DataGrid.
# Get-PoliciesFromFolder returns IntunePolicyBase / PSCustomObject hybrids
# whose nested-property bindings (Object.Name, Object.PolicyName, etc.)
# don't survive Avalonia's reflection-based binder
# (see [[avalonia-binding-needs-clr-types]]). Show-IntuneManagerImportForm
# projects each policy into one of these before assigning ItemsSource.
# Source/ImportTarget remain on .Source / .ImportTarget so the import
# pass can recover the originals.
class IntuneImportRowItem
{
[bool] $Selected = $true
[string] $ObjectName
[string] $PolicyType
[string] $PolicyBase
[string] $Platform
[string] $FileName
[string] $ImportAction
[string] $ImportMatch
[string] $ImportMatchStrategy
[object] $Source # underlying IntunePolicyBase (the .Object the WPF code reads)
[object] $ImportTarget # existing policy resolved by Resolve-IntuneImportUpdateTarget
IntuneImportRowItem() {}
}
@@ -0,0 +1,33 @@
#ImportOrder 10
# CLR-typed row shape for the Intune Manager DataGrid. The WPF implementation
# wraps each IntunePolicyBase via [PSCustomObject]$policy + Add-Member; that
# survives WPF's PSObject special-case binding but renders empty in Avalonia
# (see [[avalonia-binding-needs-clr-types]]). Show-SelectedGraphPolicies
# projects the canonical view properties onto this class before binding.
#
# Source / JsonObject / TokenId mirror the fields the bulk forms read off
# the row directly. Custom column overrides (per-type `ObjectColumns`
# setting) reach paths beyond these canonical fields and bind through
# [IntuneManagement.AvaloniaHost.IntunePolicyPathConverter] (declared in
# Bootstrap/AvaloniaHost.cs, compiled at runtime) against `Source` so
# PSObject NoteProperties on the underlying IntunePolicyBase resolve.
class IntuneObjectRowItem
{
[bool] $IsSelected = $false
[string] $Name
[string] $PolicyName
[string] $Platform
[string] $Description
[string] $LastModified
[string] $Created
[string] $ID
[string] $ScopeTags
[object] $JsonObject
[object] $Source
[int] $TokenId
IntuneObjectRowItem() {}
}
@@ -0,0 +1,48 @@
#ImportOrder 7
# Verbatim port of UI/WPF/Classes/IntuneToolsViewObject.ps1. The class shape is
# unchanged; the difference between WPF and Avalonia lives in the matching
# Extensions/IntuneToolsUI.ps1 (panel + items + tool placeholders).
class IntuneToolsViewObject : ViewObjectBase
{
IntuneToolsViewObject() : Base()
{
$this.Init()
}
Hidden Init()
{
$this._ID = "IntuneTools"
$this._Title = "Intune Tools"
$this._Description = "Tools that operate on Intune objects, such as listing assignments across all object types."
$this._AddToMenu = $true
$this._ExpandInViewsMenu = $true
}
[Object]GetViewPanel()
{
if($null -eq $this._ViewPanel)
{
$this._ViewPanel = Get-IntuneToolsViewPanel
}
return $this._ViewPanel
}
[Object[]]GetViewItems()
{
return @(Get-IntuneToolsViewItems)
}
Authenticate()
{
$active = $null
try { $active = Get-AuthProvider } catch { }
if($active) { $active.RefreshAmbientSession() }
}
[Object[]]OnItemChanged($SelectedItem)
{
return (Invoke-IntuneToolsActivateItem $SelectedItem)
}
}
+67
View File
@@ -0,0 +1,67 @@
#ImportOrder 150
# Avalonia port of UI/WPF/Classes/IntuneUIGeneric.ps1.
#
# The legacy "Intune Manager" view — main object editor / list / bulk host. In
# WPF this is the landing view (`Show-View "IntuneManagement"`); we mirror that
# here. AddToMenu=$false so it doesn't show up in the Views menu — it's added
# explicitly by Invoke-IntuneUIAppInitialized.
#
# Method bodies delegate to functions in Extensions/IntuneManagerUI.ps1
# (mirrors the WPF split). Most are still stubbed pending the data-load and
# DataGrid column ports — see CLAUDE.md "Pending work".
class IntuneViewObject : ViewObjectBase
{
Hidden [Object[]]$_Items = @()
IntuneViewObject() : Base()
{
$this.Init()
}
Hidden Init()
{
$this._ID = "IntuneManagement"
$this._Title = "Intune Manager"
$this._Description = "Manages Intune environments. This view can be used for copying objects in an Intune environment. It can also be used for backing up an entire Intune environment and cloning the Intune environment into another tenant."
$this._AddToMenu = $false
}
[Object]GetViewPanel()
{
if ($null -eq $this._ViewPanel) {
$this._ViewPanel = Get-IntuneManagementViewPanel
}
return $this._ViewPanel
}
[Object[]]GetViewItems()
{
return @(Get-IntuneViewItems)
}
Authenticate()
{
# Match WPF: silent-only at activation. Interactive auth happens via the
# Sign-in button. Gated to MSAL so MgGraph mode isn't hijacked.
$active = $null
try { $active = Get-AuthProvider } catch { }
if ($active) { $active.RefreshAmbientSession() }
}
[Object[]]OnItemChanged($SelectedItem)
{
return (Invoke-IntuneActivateObject $SelectedItem)
}
OnDeactivating($NewActiveView)
{
Invoke-IntuneDeactivatingView $NewActiveView
}
OnActivating($PreviousActiveView)
{
Invoke-IntuneActivatingView $PreviousActiveView
}
}
+22
View File
@@ -0,0 +1,22 @@
#ImportOrder 9
# CLR-typed row shape for the Log DataGrid (LogInfo.axaml). The non-UI
# Internal/Core.ps1 keeps log entries as PSCustomObject in
# $script:LogItems; Avalonia's binder can't traverse PSCustomObject inside
# DataTemplates, so Get-LogViewPanel projects each entry into one of these
# at refresh time.
#
# RowForeground is computed at projection time so the DataGrid row style
# can bind directly to it (Type==2 -> orange, Type==3 -> red, default -> null).
class LogRowItem
{
[int] $ID
[datetime] $DateTime
[int] $Type
[string] $TypeText
[string] $Text
[object] $RowForeground
LogRowItem() {}
}
@@ -0,0 +1,31 @@
#ImportOrder 10
# CLR row shape for Avalonia ComboBoxes that need DisplayMemberBinding.
#
# Avalonia's data binder reflects on the runtime CLR type, not on PSObject
# ETS NoteProperties — so a ComboBox with ItemsSource = PSCustomObject[]
# and DisplayMemberBinding="{Binding Name}" silently renders blank cells
# because PSCustomObject has no real .Name property (see
# [[avalonia-binding-needs-clr-types]]).
#
# Wrap items in this class to get real CLR Name / Value / EnglishName /
# Source properties that the Avalonia binder + PowerShell readers (e.g.
# Get-AvaloniaDocumentationOptions reading SelectedItem.Value) both see.
# Source keeps the original PSCustomObject so callers that need extra
# fields (e.g. language metadata) can pull them back.
class NameValueComboItem
{
[string] $Name
[string] $EnglishName
[object] $Value
[object] $Source
NameValueComboItem() {}
# Backstop only. Each control picks its own field via DisplayMemberBinding
# (13 combos bind Name, the language combo binds EnglishName), so this
# exists purely so a control that was never given a binding degrades to
# readable text instead of rendering "NameValueComboItem".
[string] ToString() { return [string]$this.Name }
}
+21
View File
@@ -0,0 +1,21 @@
#ImportOrder 10
# CLR row shape for Show-ObjectPickerDialog's dgPickerObjects DataGrid.
# Avalonia's binder can't reach PSObject NoteProperties (see
# [[avalonia-binding-needs-clr-types]]), so each policy fetched from
# Get-GraphPolicies is projected into one of these. Source carries the
# original PSCustomObject so callers receive it back via SelectedItem.Source.
# Currently the dialog is called with a "Name" + "PolicyType.Title" column
# pair — mapped to the Name / PolicyTypeTitle properties below. Add a new
# property + binding alias in Show-ObjectPickerDialog when a future caller
# needs another column.
class PickerObjectRow
{
[string] $Name
[string] $PolicyTypeTitle
[string] $Description
[object] $Source
PickerObjectRow() {}
}
+69
View File
@@ -0,0 +1,69 @@
#ImportOrder 5
# Sandbox view: the only ViewObjectBase subclass that loads under the Avalonia
# backend (the WPF subclasses live in UI/WPF/Classes/ and aren't touched here). It
# exists to exercise the discover -> register -> Show-View flow end-to-end while
# the real views are ported one at a time.
class PreviewViewObject : ViewObjectBase
{
Hidden [Object]$_BodyText = $null
PreviewViewObject() : base()
{
$this.Init()
}
Hidden Init()
{
$this._ID = "AvaloniaPreview"
$this._Title = "Avalonia Preview"
$this._Description = "Sandbox view to verify the Avalonia view-system port."
}
[Object]GetViewPanel()
{
# Built programmatically — no .axaml needed for a placeholder. Avalonia
# types are available because Initialize-AvaloniaRuntime runs before
# Show-View ever calls this.
if($null -eq $this._ViewPanel)
{
$panel = New-Object Avalonia.Controls.StackPanel
$panel.Margin = '20'
$panel.Spacing = 12
$title = New-Object Avalonia.Controls.TextBlock
$title.Text = "Avalonia preview view"
$title.FontSize = 22
$title.FontWeight = 'SemiBold'
$body = New-Object Avalonia.Controls.TextBlock
$body.Text = "Pick an item from the left nav - its title will appear here."
$body.TextWrapping = 'Wrap'
$panel.Children.Add($title) | Out-Null
$panel.Children.Add($body) | Out-Null
$this._BodyText = $body
$this._ViewPanel = $panel
}
return $this._ViewPanel
}
[Object[]]OnItemChanged($SelectedItem)
{
if ($null -ne $this._BodyText -and $null -ne $SelectedItem) {
$this._BodyText.Text = "Selected: $($SelectedItem.Title) (Id=$($SelectedItem.Id))"
}
return $null
}
[Object[]]GetViewItems()
{
return @(
[ViewMenuItem]@{ Id = 'demo-1'; Title = 'First demo item'; MenuLabel = 'First demo item' }
[ViewMenuItem]@{ Id = 'demo-2'; Title = 'Second demo item'; MenuLabel = 'Second demo item' }
[ViewMenuItem]@{ Id = 'demo-3'; Title = 'Third demo item'; MenuLabel = 'Third demo item' }
)
}
}
+20
View File
@@ -0,0 +1,20 @@
#ImportOrder 6
# CLR-typed shape for items inside the Settings dialog's ComboBox controls
# (Type="List" settings). Existing setting registrations pass PSCustomObject
# arrays in -ItemsSource with .Name / .Value properties — those won't bind in
# Avalonia (see [[avalonia-binding-needs-clr-types]]). Add-SettingComboBox
# converts each PSCustomObject into one of these before assigning ItemsSource.
class SettingsListItem
{
[string]$Name
[object]$Value
SettingsListItem() {}
[string] ToString()
{
return [string]$this.Name
}
}
+14
View File
@@ -0,0 +1,14 @@
#ImportOrder 10
# CLR-typed Name/Value pair shape used by Show-MSALDecodedToken's DataGrid.
# The WPF version builds an array of PSCustomObjects with Name/Value; that
# would silently render blank in Avalonia (binder can't traverse NoteProperty),
# so the Avalonia tool converts to instances of this class.
class TokenInfoRow
{
[string] $Name
[object] $Value
TokenInfoRow() {}
}
+44
View File
@@ -0,0 +1,44 @@
#ImportOrder 3
# CLR-backed shape for items rendered in the left-nav ListBox under the
# Avalonia backend. Avalonia's binder reflects against real CLR properties
# and does NOT special-case PSObject's NoteProperty bag the way WPF does, so
# binding to a [PSCustomObject] would silently resolve to null. This class
# exists to give those bindings real properties to find.
#
# Mirrors the WPF view-item shape just enough for the existing
# DataTemplate ({Binding MenuLabel} + {Binding IconImage}) plus the few
# fields Show-View / OnItemChanged read.
class ViewMenuItem
{
[string]$Id
[string]$Title
[string]$MenuLabel
[string]$Category
[string]$Description
# Permission state, stamped from the underlying type/group by
# Get-IntuneViewItems. Drives the row colour via the Classes bindings in
# MainWindow.axaml ("Limited" = orange, "None" = red); AccessInfo is the
# tooltip breakdown. Held as a string because Avalonia's Classes binding
# compares against one.
[string]$AccessType
# [object], not [string]: an empty tooltip is not the same as no tooltip.
# Avalonia shows a blank popup for ToolTip.Tip="" but nothing for $null, and
# a [string] property would coerce $null to "".
[object]$AccessInfo
# Avalonia's Classes bindings take booleans and it has no DataTrigger, so the
# string above cannot drive the style directly. Set alongside AccessType by
# Get-IntuneViewItems.
[bool]$IsAccessLimited = $false
[bool]$IsAccessNone = $false
[Object]$IconImage
[Object]$Tag
# IsHeader=true marks a category-header row inserted by Show-ViewMenu when
# rendering grouped left-nav menus. Avalonia's ListBox has no GroupStyle
# equivalent, so we fake the WPF Expander headers with non-selectable rows
# that share the same DataTemplate (template branches on IsHeader).
[bool]$IsHeader = $false
ViewMenuItem() {}
}