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
+145
View File
@@ -0,0 +1,145 @@
# Text columns for WPF DataGrids: one line per row, capped length, pixel scrolling.
#
# A DataGridTextColumn renders every line of a multi-line value, so a store app
# description makes its row forty lines tall and turns the grid's row-based
# scrolling into a lurch. TextWrapping=NoWrap does not help: it only stops soft
# wrapping, and hard line breaks still render. And a single very long line has
# the opposite problem - it widens its column until the others are pushed out of
# view. FirstLineConverter trims the bound value to its first non-blank line,
# caps it at MaxLength characters (0 = no cap), appends an ellipsis when either
# happened, and with ConverterParameter "Full" returns the whole text only when
# something was cut - so the tooltip appears exactly on the cells that hide it.
if(-not ("FirstLineConverter" -as [type]))
{
# References resolved from loaded types, so the same call compiles on
# .NET Framework (PS5.1) and .NET (PS7).
Add-Type -ReferencedAssemblies @(
[System.Windows.Data.IValueConverter].Assembly.Location, # PresentationFramework
[System.Windows.DependencyObject].Assembly.Location, # WindowsBase
[System.Windows.Markup.MarkupExtension].Assembly.Location # System.Xaml - Binding.DoNothing needs it on .NET Framework
) -TypeDefinition @'
using System;
using System.Globalization;
using System.Windows.Data;
public class FirstLineConverter : IValueConverter
{
// Maximum characters kept from the first line. 0 = unlimited.
public int MaxLength { get; set; }
// First non-blank line, capped at maxLength; "\u2026" appended when any
// later non-blank line exists or the cap cut something off.
public static string FirstLine(string text, int maxLength, out bool trimmed)
{
trimmed = false;
if (text == null) return null;
string[] lines = text.Split(new[] { '\r', '\n' });
string first = null;
foreach (string line in lines)
{
string t = line.Trim();
if (t.Length == 0) continue;
if (first == null) { first = t; continue; }
trimmed = true;
break;
}
if (first == null) return string.Empty;
if (maxLength > 0 && first.Length > maxLength)
{
// Prefer a word boundary when one falls in the second half of the
// cap, so the cell ends "...built in" rather than "...with Cop".
int cut = first.LastIndexOf(' ', maxLength);
if (cut < maxLength / 2) cut = maxLength;
first = first.Substring(0, cut).TrimEnd();
trimmed = true;
}
return trimmed ? first + "\u2026" : first;
}
// A binding to a PowerShell SCRIPT property (the row's Description, Platform,
// LastModified ...) delivers the value wrapped in a PSObject; a note property
// (Object.description) delivers the raw string. Unwrap by reflection so the
// converter sees the string either way without referencing the PowerShell
// assembly - this is what left Description rendering all its lines while
// Object.description was trimmed.
private static object Unwrap(object value)
{
if (value == null) return null;
var t = value.GetType();
if (t.FullName != "System.Management.Automation.PSObject") return value;
var p = t.GetProperty("BaseObject");
return p == null ? value : p.GetValue(value, null);
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
value = Unwrap(value);
string text = value as string;
if (text == null) return value; // dates, numbers: leave the column's own formatting alone
bool trimmed;
string first = FirstLine(text, MaxLength, out trimmed);
if (parameter != null && string.Equals(parameter.ToString(), "Full", StringComparison.OrdinalIgnoreCase))
{
return trimmed ? text : null; // tooltip only where something was hidden
}
return first;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
'@
}
# Builds a read-only text column. With -FirstLineOnly the cell shows the value's
# first line (capped at -MaxLength characters when that is above 0) and carries
# the full text as a tooltip. Sorting and the grid filter still use the
# underlying value: both read the binding path, not what is displayed.
function New-GridTextColumn
{
param(
[Parameter(Mandatory = $true)][string]$Path,
[string]$Header,
[switch]$FirstLineOnly,
[int]$MaxLength = 0
)
$column = [System.Windows.Controls.DataGridTextColumn]::new()
$column.Header = if($Header) { $Header } else { $Path.Split('.')[-1] }
$column.IsReadOnly = $true
$binding = [System.Windows.Data.Binding]::new($Path)
if($FirstLineOnly)
{
$binding.Converter = [FirstLineConverter]::new()
$binding.Converter.MaxLength = $MaxLength
$tooltip = [System.Windows.Data.Binding]::new($Path)
$tooltip.Converter = [FirstLineConverter]::new()
$tooltip.Converter.MaxLength = $MaxLength
$tooltip.ConverterParameter = 'Full'
$style = [System.Windows.Style]::new([System.Windows.Controls.TextBlock])
$style.Setters.Add([System.Windows.Setter]::new([System.Windows.Controls.TextBlock]::TextTrimmingProperty, [System.Windows.TextTrimming]::CharacterEllipsis))
$style.Setters.Add([System.Windows.Setter]::new([System.Windows.FrameworkElement]::ToolTipProperty, $tooltip))
$column.ElementStyle = $style
}
$column.Binding = $binding
return $column
}
# Pixel-based scrolling for a DataGrid built in code; the XAML grids declare
# VirtualizingPanel.ScrollUnit="Pixel" directly. WPF's default scrolls by whole
# rows and snaps the top row to the edge, which lurches as soon as rows differ in
# height. Row virtualization stays on.
function Set-GridPixelScrolling
{
param([Parameter(Mandatory = $true)]$Grid)
[System.Windows.Controls.VirtualizingPanel]::SetScrollUnit($Grid, [System.Windows.Controls.ScrollUnit]::Pixel)
[System.Windows.Controls.VirtualizingPanel]::SetVirtualizationMode($Grid, [System.Windows.Controls.VirtualizationMode]::Recycling)
}