Files
macOS_IntuneManagement/Core.psm1
Tomas Kracmar e13d14edcb feat(toolkit): complete macOS Intune Toolkit v1
Core enhancements:
- Expanded default export/import scope to ~45 object types including DeviceManagementIntents
- Added -AllPages pagination support across Graph queries for large tenants
- Invoke-GraphRequest now throws on 4xx/5xx instead of silently returning null
- Added macOS Keychain fallback for secret retrieval in headless auth flow
- Added NameSearchPattern/NameReplacePattern mutation support through export/import forms

New toolkit scripts:
- Bulk-AppAssignment.ps1: bulk-assign apps to groups/All Users/All Devices
- Bulk-AssignmentManager.ps1: add/remove assignments for any policy type with correct @odata.type
- Backup-Restore-Assignments.ps1: JSON backup with cross-tenant group resolution
- Export-AssignmentsToCsv.ps1: CSV/Markdown documentation output
- Bulk-RenamePolicies.ps1: regex search/replace and prefix mutations
- Bulk-DeviceOperations.ps1: delete/retire/wipe/lock/sync with -WhatIf safeguards
- Start-IntuneManagementTui.ps1: interactive terminal UI for headless operations
- Create-IntuneManagementApp.ps1: helper for app registration setup

Updated existing scripts:
- Export-Policies.ps1 / Import-Policies.ps1: wired mutation params through
- Start-HeadlessIntune.ps1: integrated TUI and new parameter forwarding
2026-04-14 15:11:09 +02:00

1051 lines
25 KiB
PowerShell

<#
.SYNOPSIS
Headless runtime helpers for macOS Intune Management.
.DESCRIPTION
This module provides the non-UI runtime used by the CLI entrypoints.
#>
# Microsoft.Graph.Authentication registers an alias Invoke-GraphRequest -> Invoke-MgGraphRequest.
# Remove it so our local function in MSGraph.psm1 is used instead.
if (Get-Alias Invoke-GraphRequest -ErrorAction SilentlyContinue)
{
Remove-Item Alias:\Invoke-GraphRequest -Force -ErrorAction SilentlyContinue
}
function Get-ModuleVersion
{
'4.0.0'
}
function Test-IsWindowsPlatform
{
[Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT
}
function Invoke-AppDoEvents
{
}
function Expand-FileName
{
param([string]$Path)
if(-not $Path) { return $Path }
$expanded = [Environment]::ExpandEnvironmentVariables($Path)
if($expanded -like "~/*" -or $expanded -eq "~")
{
$expanded = $expanded -replace "^~", $HOME
}
return $expanded
}
function Remove-InvalidFileNameChars
{
param([string]$Name)
if([string]::IsNullOrEmpty($Name)) { return $Name }
$invalid = [IO.Path]::GetInvalidFileNameChars()
foreach($char in $invalid)
{
$Name = $Name.Replace($char, '_')
}
# Also replace path separator if present (relevant on Unix)
$Name = $Name.Replace('/', '_')
$Name
}
function Remove-Property
{
param($Object, [string]$PropertyName)
if(-not $Object -or [string]::IsNullOrEmpty($PropertyName)) { return }
if($Object.PSObject.Properties[$PropertyName])
{
$Object.PSObject.Properties.Remove($PropertyName)
}
}
function Start-CoreApp
{
param($View)
$global:hideUI = $true
$global:useDefaultFolderDialog = $false
$global:WindowsAPICodePackLoaded = $false
$global:loadedModules = @()
$global:viewObjects = @()
$global:AppRootFolder = $PSScriptRoot
$global:modulesPath = Join-Path $PSScriptRoot "Extensions"
Add-DefaultSettings
if(-not $global:UseJSonSettings)
{
$global:UseJSonSettings = $true
}
Initialize-JsonSettings
Initialize-Settings
Write-Log "#####################################################################################"
Write-Log "Application started"
Write-Log "#####################################################################################"
Write-Log "PowerShell version: $($PSVersionTable.PSVersion)"
Write-Log "PowerShell edition: $($PSVersionTable.PSEdition)"
Write-Log "OS version: $([Environment]::OSVersion.VersionString)"
if(-not (Test-Path $global:modulesPath))
{
Write-Log "Extensions folder $($global:modulesPath) not found. Aborting..." 3
return
}
Import-AllModules
$global:currentViewObject = $null
$global:FirstTimeRunning = $false
$global:MainAppStarted = $true
Invoke-ModuleFunction "Invoke-InitializeModule"
if($View)
{
$global:currentViewObject = $global:viewObjects | Where-Object { $_.ViewInfo.Id -eq $View } | Select-Object -First 1
}
if(-not $global:currentViewObject)
{
$global:currentViewObject = $global:viewObjects | Select-Object -First 1
}
if(-not $global:SilentBatchFile)
{
Write-Log "SilentBatchFile must be specified" 3
return
}
$silentFile = [IO.FileInfo]$global:SilentBatchFile
if(-not $silentFile.Exists)
{
Write-Log "SilentBatchFile $($global:SilentBatchFile) not found" 3
return
}
Invoke-ModuleFunction "Invoke-InitSilentBatchJob"
Start-RunSilentBatchJob
}
function Start-RunSilentBatchJob
{
try
{
$settingsObj = ConvertFrom-Json (Get-Content -Path $global:SilentBatchFile -Raw -ErrorAction Stop)
Invoke-ModuleFunction "Invoke-SilentBatchJob" $settingsObj
}
catch
{
Write-LogError "Failed to trigger silent batch job." $_.Exception
}
}
function Import-AllModules
{
foreach($file in (Get-Item -Path (Join-Path $global:modulesPath "*.psm1")))
{
$module = Import-Module $file.FullName -PassThru -Force -Global -ErrorAction Stop
$global:loadedModules += $module
Write-Host "Module $($module.Name) loaded successfully"
}
}
function Write-Log
{
param($Text, $Type = 1)
if($script:logFailed -eq $true) { return }
if(-not $global:logFile)
{
$defaultLogFile = Join-Path (Get-CloudApiDataFolder) "IntuneManagement.log"
$global:logFile = Get-SettingValue "LogFile" $defaultLogFile
}
if(-not $global:logFileMaxSize)
{
[Int64]$global:logFileMaxSize = Get-SettingValue "LogFileSize" 1024
$global:logFileMaxSize *= 1kb
}
if($null -eq $global:logOutputError)
{
$global:logOutputError = Get-SettingValue "LogOutputError" $true
}
try
{
$logPath = [IO.Path]::GetDirectoryName($global:logFile)
if($logPath -and -not (Test-Path $logPath))
{
New-Item -ItemType Directory -Path $logPath -Force -ErrorAction Stop | Out-Null
}
}
catch
{
$script:logFailed = $true
return
}
$date = Get-Date
$component = [IO.Path]::GetFileNameWithoutExtension($PSCommandPath)
if(-not $component) { $component = "IntuneManagement" }
$timeStr = "{0:HH}:{0:mm}:{0:ss}.000+000" -f $date
$dateStr = "{0:MM}-{0:dd}-{0:yyyy}" -f $date
$logOut = "<![LOG[$Text]LOG]!><time=""$timeStr"" date=""$dateStr"" component=""$component"" context="""" type=""$Type"" thread=""$PID"" file=""$component"">"
switch($Type)
{
2 { Write-Warning $Text; $typeText = "Warning" }
3 {
if($global:logOutputError -ne $false) { $host.UI.WriteErrorLine($Text) }
else { Write-Warning $Text }
$typeText = "Error"
}
default { Write-Host $Text; $typeText = "Info" }
}
if(-not $script:LogItems)
{
$script:LogItems = [System.Collections.ObjectModel.ObservableCollection[object]]::new()
}
$script:LogItems.Add([PSCustomObject]@{
ID = ($script:LogItems.Count + 1)
DateTime = $date
Type = $Type
TypeText = $typeText
Text = $Text
})
try
{
$logFileInfo = [IO.FileInfo]$global:logFile
if($logFileInfo.Exists -and $logFileInfo.Length -gt $global:logFileMaxSize)
{
$bakFile = Join-Path $logFileInfo.DirectoryName "$($logFileInfo.BaseName).lo_"
if(Test-Path $bakFile)
{
Remove-Item -LiteralPath $bakFile -Force -ErrorAction SilentlyContinue
}
$logFileInfo.MoveTo($bakFile)
}
Out-File -FilePath $global:logFile -Append -Encoding ascii -InputObject $logOut
}
catch
{
}
}
function Write-LogDebug
{
param($Text, $Type = 1)
if($global:Debug)
{
Write-Log ("Debug: " + $Text) $Type
}
}
function Write-LogError
{
param($Text, $Exception)
if($Text -and $Exception.Message)
{
$Text += " Exception: $($Exception.Message)"
}
Write-Log $Text 3
if((Get-SettingValue "ShowStackTrace" $false) -eq $true -and $Exception)
{
Write-Log "Stack trace:`n$($Exception.StackTrace)"
Write-Log "Script stack trace:`n$($Exception.ScriptStackTrace)"
}
}
function Write-Status
{
param($Text, [switch]$SkipLog, [switch]$Block, [switch]$Force)
if(-not $Text)
{
$global:BlockStatusUpdates = $false
return
}
if($global:BlockStatusUpdates -and $Force -ne $true)
{
return
}
if($Block)
{
$global:BlockStatusUpdates = $true
}
if($SkipLog -ne $true)
{
Write-Log $Text
}
}
function Set-XamlProperty
{
param($Parent, $ControlName, $PropertyName, $Value)
if(-not $Parent) { return }
try
{
$control = $Parent
if($ControlName)
{
$control = $Parent.FindName($ControlName)
}
if(-not $control)
{
return
}
if($control.PSObject.Properties.Name -contains $PropertyName)
{
$control.$PropertyName = $Value
}
else
{
$control | Add-Member -NotePropertyName $PropertyName -NotePropertyValue $Value -Force
}
}
catch
{
Write-LogError "Failed to set property value. Control: $ControlName. Property: $PropertyName." $_.Exception
}
}
function Get-XamlProperty
{
param($Parent, $ControlName, $PropertyName, $DefaultValue)
if(-not $Parent) { return $DefaultValue }
try
{
$control = $Parent
if($ControlName)
{
$control = $Parent.FindName($ControlName)
}
if(-not $control)
{
return $DefaultValue
}
if($control.PSObject.Properties.Name -contains $PropertyName)
{
return (?? $control.$PropertyName $DefaultValue)
}
}
catch
{
Write-LogError "Failed to get property value. Control: $ControlName. Property: $PropertyName." $_.Exception
}
$DefaultValue
}
function Set-BatchProperties
{
param($SettingsObj, $Form, [switch]$SkipMissingControlWarning)
if(-not $SettingsObj -or -not $Form)
{
return
}
foreach($prop in $SettingsObj)
{
if($prop.Type -eq "Custom") { continue }
$obj = $Form.FindName($prop.Name)
if(-not $obj)
{
if($SkipMissingControlWarning -ne $true)
{
Write-Log "No setting for $($prop.Name) found" 2
}
continue
}
if($prop.Value -is [string] -and [string]::IsNullOrEmpty($prop.Value))
{
continue
}
try
{
if($obj.PSObject.Properties.Name -contains "IsChecked")
{
$obj.IsChecked = $prop.Value -eq $true
}
elseif($obj.PSObject.Properties.Name -contains "Text")
{
$obj.Text = $prop.Value
}
elseif($obj.PSObject.Properties.Name -contains "SelectedValue")
{
$obj.SelectedValue = $prop.Value
}
elseif($obj.PSObject.Properties.Name -contains "ItemsSource")
{
$obj.ItemsSource = $prop.Value
}
else
{
Write-Log "Unsupported object type for silent batch job: $($obj.GetType().FullName)" 3
}
}
catch
{
Write-LogError "Failed to set batch job property for $($prop.Name)" $_.Exception
}
}
}
function New-HeadlessControl
{
param(
[string]$Name,
[ValidateSet("TextBox","CheckBox","ComboBox","Label","DataGrid")]
[string]$Type = "TextBox"
)
$control = [PSCustomObject]@{
Name = $Name
Type = $Type
Focusable = ($Type -ne "DataGrid")
Visibility = "Visible"
IsEnabled = $true
Parent = $null
DataContext = $null
}
switch($Type)
{
"TextBox" {
$control | Add-Member -NotePropertyName Text -NotePropertyValue ""
}
"CheckBox" {
$control | Add-Member -NotePropertyName IsChecked -NotePropertyValue $false
}
"ComboBox" {
$control | Add-Member -NotePropertyName SelectedValue -NotePropertyValue $null
$control | Add-Member -NotePropertyName SelectedIndex -NotePropertyValue -1
$control | Add-Member -NotePropertyName SelectedItem -NotePropertyValue $null
$control | Add-Member -NotePropertyName SelectedItems -NotePropertyValue @()
$control | Add-Member -NotePropertyName ItemsSource -NotePropertyValue @()
$control | Add-Member -NotePropertyName Items -NotePropertyValue @()
}
"Label" {
$control | Add-Member -NotePropertyName Content -NotePropertyValue ""
}
"DataGrid" {
$control | Add-Member -NotePropertyName ItemsSource -NotePropertyValue @()
$control | Add-Member -NotePropertyName Columns -NotePropertyValue @()
$control | Add-Member -NotePropertyName SelectedItems -NotePropertyValue @()
$control | Add-Member -NotePropertyName SelectedItem -NotePropertyValue $null
}
}
$control
}
function New-HeadlessForm
{
param($Controls)
$form = [PSCustomObject]@{
Controls = @{}
}
foreach($control in $Controls)
{
$control.Parent = $form
$form.Controls[$control.Name] = $control
}
$form | Add-Member -MemberType ScriptMethod -Name FindName -Value {
param($Name)
$this.Controls[$Name]
}
$form | Add-Member -MemberType ScriptMethod -Name RegisterName -Value {
param($Name, $Control)
$Control.Parent = $this
$this.Controls[$Name] = $Control
}
$form
}
function Initialize-Window
{
param($XamlFile)
throw "UI support has been removed from this project."
}
function Show-ModalForm
{
param($FormTitle, $Form, [switch]$HideButtons)
}
function Show-ModalObject
{
}
function Get-Folder
{
param($CurrentFolder, $Description)
if($CurrentFolder -and (Test-Path $CurrentFolder))
{
return $CurrentFolder
}
throw "Folder prompts are not supported in the headless runtime."
}
function Get-CloudApiDataFolder
{
if(Test-IsWindowsPlatform)
{
if($env:LOCALAPPDATA)
{
return (Join-Path $env:LOCALAPPDATA "macOS_IntuneManagement")
}
return (Join-Path $env:USERPROFILE "AppData/Local/macOS_IntuneManagement")
}
if($IsMacOS)
{
return (Join-Path $HOME "Library/Application Support/macOS_IntuneManagement")
}
return (Join-Path $HOME ".local/share/macOS_IntuneManagement")
}
function Initialize-Settings
{
param([switch]$Updated)
$global:Debug = Get-SettingValue "Debug" $false
$global:logFile = $null
$global:logFileMaxSize = $null
$global:logOutputError = $null
$script:proxyURI = $null
if($Updated -eq $true)
{
Invoke-ModuleFunction "Invoke-SettingsUpdated"
}
}
function Initialize-JsonSettings
{
if(-not $global:JSonSettingFile)
{
$global:JSonSettingFile = Join-Path (Get-CloudApiDataFolder) "Settings.json"
}
try
{
$settingsDir = Split-Path -Parent $global:JSonSettingFile
if($settingsDir -and -not (Test-Path $settingsDir))
{
New-Item -ItemType Directory -Path $settingsDir -Force -ErrorAction Stop | Out-Null
}
if(-not (Test-Path $global:JSonSettingFile))
{
@{} | ConvertTo-Json | Out-File -LiteralPath $global:JSonSettingFile -Force -Encoding utf8
}
$raw = Get-Content -Path $global:JSonSettingFile -Raw -ErrorAction Stop
if([string]::IsNullOrWhiteSpace($raw))
{
$raw = "{}"
}
$global:JsonSettingsObj = ConvertFrom-Json $raw -AsHashtable
Write-Host "Use json settings file: $($global:JSonSettingFile)"
}
catch
{
Clear-JsonSettingsValues
Write-LogError "Failed to read json setting file $($global:JSonSettingFile)." $_.Exception
}
}
function Clear-JsonSettingsValues
{
$global:JsonSettingsObj = @{}
$global:JSonSettingFile = $null
$global:UseJSonSettings = $false
}
function Get-JsonSettingsNode
{
param(
[string]$SubPath,
[switch]$Create
)
if($null -eq $global:JsonSettingsObj)
{
$global:JsonSettingsObj = @{}
}
$node = $global:JsonSettingsObj
$parts = @()
if($SubPath)
{
$parts = $SubPath.TrimEnd(@('/','\')).Split(@('/','\'), [System.StringSplitOptions]::RemoveEmptyEntries)
}
foreach($part in $parts)
{
if(-not $node.ContainsKey($part))
{
if(-not $Create)
{
return $null
}
$node[$part] = @{}
}
elseif($node[$part] -isnot [System.Collections.IDictionary])
{
if(-not $Create)
{
return $null
}
$node[$part] = @{}
}
$node = $node[$part]
}
$node
}
function Save-Setting
{
param($SubPath = "", $Key = "", $Value, $Type = "String")
if(-not $Key)
{
return
}
try
{
$parent = Get-JsonSettingsNode -SubPath $SubPath -Create
if($null -eq $Value)
{
$parent.Remove($Key) | Out-Null
}
else
{
switch($Type)
{
"String" { $Value = $Value.ToString() }
"DWord" { $Value = [int]$Value }
}
$parent[$Key] = $Value
}
$global:JsonSettingsObj | ConvertTo-Json -Depth 30 | Out-File -LiteralPath $global:JSonSettingFile -Force -Encoding utf8
}
catch
{
Write-LogError "Failed to save json setting value $Key" $_.Exception
}
}
function Get-Setting
{
param($SubPath = "", $Key = "", $DefaultValue)
if(-not $Key)
{
return
}
try
{
$parent = Get-JsonSettingsNode -SubPath $SubPath
if($parent -and $parent.ContainsKey($Key))
{
$value = $parent[$Key]
if($null -ne $value)
{
return $value
}
}
}
catch
{
Write-LogError "Failed to read json setting value $Key" $_.Exception
}
$DefaultValue
}
function Add-DefaultSettings
{
$global:appSettingSections = @()
$global:appSettingSections += [PSCustomObject]@{
Title = "General"
Id = "General"
Values = @()
}
Add-SettingsObject ([PSCustomObject]@{
Title = "Log file"
Key = "LogFile"
Type = "File"
DefaultValue = (Join-Path (Get-CloudApiDataFolder) "IntuneManagement.log")
}) "General"
Add-SettingsObject ([PSCustomObject]@{
Title = "Max log file size"
Key = "LogFileSize"
Type = "Int"
DefaultValue = 1024
}) "General"
Add-SettingsObject ([PSCustomObject]@{
Title = "Add errors to PowerShell output"
Key = "LogOutputError"
Type = "Boolean"
DefaultValue = $true
}) "General"
Add-SettingsObject ([PSCustomObject]@{
Title = "Show stack trace"
Key = "ShowStackTrace"
Type = "Boolean"
DefaultValue = $false
}) "General"
Add-SettingsObject ([PSCustomObject]@{
Title = "Debug"
Key = "Debug"
Type = "Boolean"
DefaultValue = $false
}) "General"
Add-SettingsObject ([PSCustomObject]@{
Title = "Proxy URI"
Key = "ProxyURI"
Type = "String"
DefaultValue = ""
}) "General"
}
function Add-SettingsObject
{
param($Obj, $Section)
$targetSection = $global:appSettingSections | Where-Object Id -eq $Section
if(-not $targetSection)
{
Write-Log "Could not find section $Section" 3
return
}
if(-not ($Obj.PSObject.Properties.Name -contains "SubPath"))
{
$Obj | Add-Member -NotePropertyName "SubPath" -NotePropertyValue ""
}
$targetSection.Values += $Obj
}
function Get-SettingValue
{
param($Key, $DefaultValue, [switch]$GlobalOnly, [switch]$TenantOnly, $TenantID)
$settingObj = $null
foreach($section in $global:appSettingSections)
{
$settingObj = $section.Values | Where-Object Key -eq $Key | Select-Object -First 1
if($settingObj) { break }
}
if($null -eq $DefaultValue -and $settingObj)
{
$DefaultValue = $settingObj.DefaultValue
}
if(-not $TenantID -and $global:Organization)
{
$TenantID = $global:Organization.Id
}
$value = $null
if($settingObj)
{
if($GlobalOnly -ne $true -and $TenantID)
{
$tenantPath = if($settingObj.SubPath) { Join-Path $TenantID $settingObj.SubPath } else { $TenantID }
$value = Get-Setting $tenantPath $settingObj.Key
}
if($null -eq $value -and $TenantOnly -ne $true)
{
$value = Get-Setting $settingObj.SubPath $settingObj.Key $DefaultValue
}
}
else
{
$value = Get-Setting "" $Key $DefaultValue
}
if($settingObj)
{
switch($settingObj.Type)
{
"Boolean" { $value = ($value -eq $true -or $value -eq "true") }
"Int" {
try { $value = [int]$value }
catch { $value = $DefaultValue }
}
}
if($settingObj.PSObject.Properties.Name -contains "Value")
{
$settingObj.Value = $value
}
else
{
$settingObj | Add-Member -NotePropertyName "Value" -NotePropertyValue $value
}
}
$value
}
function Add-ViewObject
{
param($ViewObject)
$global:viewObjects += [PSCustomObject]@{
ViewInfo = $ViewObject
ViewItems = @()
}
}
function Add-ViewItem
{
param($ViewItem)
$viewObject = $global:viewObjects | Where-Object { $_.ViewInfo.Id -eq $ViewItem.ViewID } | Select-Object -First 1
if(-not $viewObject)
{
Write-Log "Could not find menu with id $($ViewItem.ViewID). Item $($ViewItem.Title) not added" 2
return
}
if(-not ($ViewItem.PSObject.Properties.Name -contains "ImportOrder"))
{
$ViewItem | Add-Member -NotePropertyName "ImportOrder" -NotePropertyValue 1000
}
foreach($scope in @($ViewItem.Permissons))
{
if($scope -and $viewObject.ViewInfo.Permissions -notcontains $scope)
{
$viewObject.ViewInfo.Permissions += $scope
}
}
$viewObject.ViewItems += $ViewItem
}
function Invoke-ModuleFunction
{
param($Function, $Arguments = $null)
Write-Log "Trigger function $Function"
$params = @{}
if($null -ne $Arguments)
{
$params.ArgumentList = $Arguments
}
foreach($module in $global:loadedModules)
{
$cmd = $module.ExportedFunctions[$Function]
if($cmd)
{
Write-Log "Trigger $Function in $($module.Name)"
Invoke-Command -ScriptBlock $cmd.ScriptBlock @params
}
}
}
function Invoke-Coalesce ($Value, $Default)
{
if($null -eq $Value) { return $Default }
if($Value -is [string] -and [string]::IsNullOrEmpty($Value)) { return $Default }
$Value
}
function Invoke-IfTrue ($Expression, $ValueIfTrue, $ValueIfFalse)
{
if($Expression) { return $ValueIfTrue }
$ValueIfFalse
}
function Get-JWTtoken
{
param($Token)
if(-not $Token) { return }
if(-not $Token.StartsWith("eyJ"))
{
Write-Log "Invalid JWT token" 3
return
}
$parts = $Token.Split(".")
if($parts.Count -lt 2)
{
Write-Log "Invalid token" 3
return
}
$header = $parts[0].Replace('-', '+').Replace('_', '/')
while($header.Length % 4) { $header += "=" }
$payload = $parts[1].Replace('-', '+').Replace('_', '/')
while($payload.Length % 4) { $payload += "=" }
[PSCustomObject]@{
Header = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($header)) | ConvertFrom-Json)
Payload = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($payload)) | ConvertFrom-Json)
}
}
function Get-ProxyURI
{
if($null -eq $script:proxyURI)
{
$script:proxyURI = Get-SettingValue "ProxyURI"
}
if($null -eq $script:proxyURI)
{
$script:proxyURI = ""
}
$script:proxyURI
}
function Start-DownloadFile
{
param($SourceURL, $TargetFile)
Write-Log "Download file from $SourceURL"
if(-not $SourceURL -or -not $TargetFile)
{
return
}
$proxyURI = Get-ProxyURI
$webClient = [System.Net.WebClient]::new()
$webClient.Encoding = [System.Text.Encoding]::UTF8
if($proxyURI)
{
$webClient.Proxy = [System.Net.WebProxy]::new($proxyURI)
}
try
{
Write-Status "Download file: $SourceURL"
$webClient.DownloadFile($SourceURL, $TargetFile)
Write-Log "File downloaded to $TargetFile"
}
catch
{
Write-LogError "Failed to download file" $_.Exception
}
finally
{
$webClient.Dispose()
}
}
function Get-ASCIIBytes
{
param($String)
$bytes = [System.Text.Encoding]::ASCII.GetBytes($String)
if($bytes[0] -eq 0x2b -and $bytes[1] -eq 0x2f -and $bytes[2] -eq 0x76)
{
return [Text.Encoding]::UTF7.GetBytes($String)
}
elseif($bytes[0] -eq 0xff -and $bytes[1] -eq 0xfe)
{
return [Text.Encoding]::Unicode.GetBytes($String)
}
elseif($bytes[0] -eq 0xfe -and $bytes[1] -eq 0xff)
{
return [Text.Encoding]::BigEndianUnicode.GetBytes($String)
}
elseif($bytes[0] -eq 0x00 -and $bytes[1] -eq 0x00 -and $bytes[2] -eq 0xfe -and $bytes[3] -eq 0xff)
{
return [Text.Encoding]::UTF32.GetBytes($String)
}
elseif($bytes[0] -eq 0xef -and $bytes[1] -eq 0xbb -and $bytes[2] -eq 0xbf)
{
return [Text.Encoding]::UTF8.GetBytes($String)
}
$bytes
}
function Get-GUIDs
{
param($Text)
$regExpGuid = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
$uniqueGuids = [System.Collections.Generic.HashSet[string]]::new()
[regex]::Matches($Text, $regExpGuid) | ForEach-Object { $uniqueGuids.Add($_.Value) | Out-Null }
$uniqueGuids
}
New-Alias -Name ?? -Value Invoke-Coalesce
New-Alias -Name ?: -Value Invoke-IfTrue
Export-ModuleMember -Alias * -Function *