HomeAssistantX.psm1
|
# HomeAssistantX bootstrapper # Auto-generated by PowerForge. Do not edit. # PowerForge script preamble begin # PowerForge script preamble end $PowerForgeModuleRoot = $PSScriptRoot $PowerForgeModulePath = $PSCommandPath $LibraryFileNames = @('HomeAssistantX.PowerShell.dll') $LibRoot = [IO.Path]::Combine($PowerForgeModuleRoot, 'Lib') $AssemblyFolders = Get-ChildItem -LiteralPath $LibRoot -Directory -ErrorAction SilentlyContinue $Default = $false $Core = $false $Standard = $false $HasNamedCorePayload = $false foreach ($AssemblyFolder in @($AssemblyFolders.Name)) { if ($AssemblyFolder -eq 'Default') { $Default = $true } elseif ($AssemblyFolder -eq 'Core') { $Core = $true } elseif ($AssemblyFolder -eq 'Standard') { $Standard = $true } elseif ($AssemblyFolder -match '^Core-(?:net|netcoreapp)\d+\.\d+$') { $HasNamedCorePayload = $true } } $Framework = if ($Standard) { 'Standard' } elseif ($Core) { 'Core' } else { '' } $FrameworkNet = if ($Default) { 'Default' } elseif ($Standard) { 'Standard' } else { '' } if ($PSEdition -eq 'Core') { $PowerForgeRuntimeVersion = [Environment]::Version $PowerForgeCoreBaselineVersion = $null if ($Core) { $PowerForgeCoreMarkerPath = [IO.Path]::Combine($LibRoot, 'Core', 'PowerForge.TargetFramework.txt') if (Test-Path -LiteralPath $PowerForgeCoreMarkerPath -PathType Leaf) { try { $PowerForgeCoreTargetFramework = [IO.File]::ReadAllText($PowerForgeCoreMarkerPath).Trim() if ($PowerForgeCoreTargetFramework -match '^net(?:coreapp)?(\d+\.\d+)$') { $PowerForgeCoreBaselineVersion = [Version]$Matches[1] } } catch { $PowerForgeCoreBaselineVersion = $null } } } $PowerForgeSelectedRuntimeVersion = [Version]'0.0' $PowerForgeSelectedRuntimeFolder = $null if ($Core -and $null -ne $PowerForgeCoreBaselineVersion -and $PowerForgeCoreBaselineVersion -le $PowerForgeRuntimeVersion) { $PowerForgeSelectedRuntimeVersion = $PowerForgeCoreBaselineVersion $PowerForgeSelectedRuntimeFolder = 'Core' } foreach ($PowerForgeRuntimeFolder in @($AssemblyFolders.Name)) { if ($PowerForgeRuntimeFolder -notmatch '^Core-(?:net|netcoreapp)(\d+\.\d+)$') { continue } try { $PowerForgeCandidateRuntimeVersion = [Version]$Matches[1] } catch { continue } if ($PowerForgeCandidateRuntimeVersion -le $PowerForgeRuntimeVersion -and $PowerForgeCandidateRuntimeVersion -gt $PowerForgeSelectedRuntimeVersion -and (Test-Path -LiteralPath ([IO.Path]::Combine($LibRoot, $PowerForgeRuntimeFolder)))) { $PowerForgeSelectedRuntimeVersion = $PowerForgeCandidateRuntimeVersion $PowerForgeSelectedRuntimeFolder = $PowerForgeRuntimeFolder } } if (-not [string]::IsNullOrWhiteSpace($PowerForgeSelectedRuntimeFolder)) { $Framework = $PowerForgeSelectedRuntimeFolder } elseif ($Core -and (-not $Standard -or ($null -ne $PowerForgeCoreBaselineVersion -and $PowerForgeCoreBaselineVersion -le $PowerForgeRuntimeVersion))) { $Framework = 'Core' } elseif ($Standard) { $Framework = 'Standard' } } $PowerForgeHasNoCompatibleNamedCorePayload = $PSEdition -eq 'Core' -and $HasNamedCorePayload -and ($Framework -eq 'Default' -or [string]::IsNullOrWhiteSpace($Framework)) $PowerForgePreferredBinaryFolders = if ($PSEdition -eq 'Core') { @($Framework, 'Standard', 'Core', '', 'Default') } else { @($FrameworkNet, 'Default', 'Standard', '', 'Core') } $PowerForgePreferredBinaryFolders = @($PowerForgePreferredBinaryFolders | Where-Object { $null -ne $_ } | Select-Object -Unique) $ResolvePowerForgeModuleAssembly = { param([Parameter(Mandatory = $true)][string] $LibraryFileName) if ([IO.Path]::IsPathRooted($LibraryFileName)) { $AbsoluteMatch = Get-Item -LiteralPath $LibraryFileName -ErrorAction SilentlyContinue if ($null -ne $AbsoluteMatch -and -not $AbsoluteMatch.PSIsContainer) { return [pscustomobject]@{ Path = $AbsoluteMatch.FullName Folder = $AbsoluteMatch.DirectoryName Directory = $AbsoluteMatch.DirectoryName } } } elseif ($LibraryFileName.IndexOfAny([char[]]@([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar)) -ge 0) { $RelativeReference = $LibraryFileName.Replace([IO.Path]::AltDirectorySeparatorChar, [IO.Path]::DirectorySeparatorChar) $LibPrefix = 'Lib' + [IO.Path]::DirectorySeparatorChar if ($RelativeReference.StartsWith($LibPrefix, [StringComparison]::OrdinalIgnoreCase)) { $RelativeReference = $RelativeReference.Substring($LibPrefix.Length) } $QualifiedMatch = @(Get-ChildItem -LiteralPath $LibRoot -File -Recurse -ErrorAction SilentlyContinue | Where-Object { $RelativeCandidate = $_.FullName.Substring($LibRoot.Length).TrimStart([char[]]@([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar)) $RelativeCandidate -ieq $RelativeReference } | Select-Object -First 1)[0] if ($null -ne $QualifiedMatch) { return [pscustomobject]@{ Path = $QualifiedMatch.FullName Folder = [IO.Path]::GetDirectoryName($RelativeReference) Directory = [IO.Path]::GetDirectoryName($QualifiedMatch.FullName) } } } foreach ($Folder in $PowerForgePreferredBinaryFolders) { $Directory = if ([string]::IsNullOrWhiteSpace($Folder)) { $LibRoot } else { [IO.Path]::Combine($LibRoot, $Folder) } if (-not (Test-Path -LiteralPath $Directory -PathType Container)) { continue } $Match = @(Get-ChildItem -LiteralPath $Directory -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -ieq $LibraryFileName } | Select-Object -First 1)[0] if ($null -ne $Match) { return [pscustomobject]@{ Path = $Match.FullName Folder = $Folder Directory = [IO.Path]::GetDirectoryName($Match.FullName) } } } $RecursiveMatches = @(Get-ChildItem -LiteralPath $LibRoot -File -Recurse -ErrorAction SilentlyContinue | Where-Object { if ($_.Name -ine $LibraryFileName) { return $false } if (-not $PowerForgeHasNoCompatibleNamedCorePayload) { return $true } $RelativeCandidate = $_.FullName.Substring($LibRoot.Length).TrimStart([char[]]@([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar)) $TopLevelFolder = @($RelativeCandidate -split '[\\/]')[0] $TopLevelFolder -notmatch '^Core-(?:net|netcoreapp)\d+\.\d+$' }) if ($RecursiveMatches.Count -eq 1) { $RecursiveMatch = $RecursiveMatches[0] $RelativeDirectory = $RecursiveMatch.DirectoryName.Substring($LibRoot.Length).TrimStart([char[]]@([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar)) return [pscustomobject]@{ Path = $RecursiveMatch.FullName Folder = $RelativeDirectory Directory = $RecursiveMatch.DirectoryName } } if ($RecursiveMatches.Count -gt 1) { throw "Configured binary module '$LibraryFileName' matched multiple nested Lib payloads. Use a path-qualified ExportAssemblies entry." } if ($PowerForgeHasNoCompatibleNamedCorePayload) { throw 'No compatible PowerShell Core assemblies found' } throw "Configured binary module '$LibraryFileName' was not found in a compatible Lib layout." } $UnregisterPowerForgeDesktopAssemblyResolver = $null $PowerForgeDesktopAssemblyRoots = @() if ($PSEdition -ne 'Core') { if ($null -ne $ResolvePowerForgeModuleAssembly -and $LibraryFileNames.Count -gt 0) { foreach ($PowerForgeDesktopLibraryFileName in $LibraryFileNames) { try { $PowerForgeDesktopResolvedModule = & $ResolvePowerForgeModuleAssembly -LibraryFileName $PowerForgeDesktopLibraryFileName } catch { Write-Verbose "Skipping Desktop resolver root for '$PowerForgeDesktopLibraryFileName'. $($_.Exception.Message)" continue } if ($PowerForgeDesktopResolvedModule.Directory -notin $PowerForgeDesktopAssemblyRoots) { $PowerForgeDesktopAssemblyRoots += $PowerForgeDesktopResolvedModule.Directory } } } elseif ($LibFolder -or $Root) { $PowerForgeDesktopAssemblyRoots += if ($LibFolder) { [IO.Path]::GetFullPath([IO.Path]::Combine($PSScriptRoot, 'Lib', $LibFolder)) } else { [IO.Path]::GetFullPath([IO.Path]::Combine($PSScriptRoot, 'Lib')) } } } if ($PSEdition -ne 'Core' -and $PowerForgeDesktopAssemblyRoots.Count -gt 0) { $PowerForgeDesktopAssemblyRootPrefixes = @($PowerForgeDesktopAssemblyRoots | ForEach-Object { $PowerForgeDesktopAssemblyRootPrefix = [IO.Path]::GetFullPath($_) if (-not $PowerForgeDesktopAssemblyRootPrefix.EndsWith([IO.Path]::DirectorySeparatorChar.ToString(), [StringComparison]::Ordinal)) { $PowerForgeDesktopAssemblyRootPrefix += [IO.Path]::DirectorySeparatorChar } $PowerForgeDesktopAssemblyRootPrefix }) if ($PowerForgeDesktopAssemblyRootPrefixes.Count -eq 0) { $PowerForgeDesktopAssemblyRoots = @() } $PowerForgeDesktopAssemblyResolverState = [pscustomobject]@{ BootstrapActive = $true Registered = $false } $PowerForgeDesktopAssemblyResolver = [System.ResolveEventHandler] { param([object] $Sender, [ResolveEventArgs] $EventArgs) try { if ($null -eq $EventArgs) { return $null } # AssemblyResolve is AppDomain-wide on Windows PowerShell. During the # bounded preload/import window the CLR can omit RequestingAssembly # while reconciling netstandard dependency versions. Outside that # window, only service requests attributable to this module's private # Lib folder. if ($null -eq $EventArgs.RequestingAssembly -or [string]::IsNullOrWhiteSpace($EventArgs.RequestingAssembly.Location)) { if (-not $PowerForgeDesktopAssemblyResolverState.BootstrapActive) { return $null } } else { $PowerForgeRequestingAssemblyPath = [IO.Path]::GetFullPath($EventArgs.RequestingAssembly.Location) $PowerForgeRequestFromModuleRoot = @($PowerForgeDesktopAssemblyRootPrefixes | Where-Object { $PowerForgeRequestingAssemblyPath.StartsWith($_, [StringComparison]::OrdinalIgnoreCase) }).Count -gt 0 if (-not $PowerForgeRequestFromModuleRoot) { return $null } } $PowerForgeRequestedAssemblyName = [Reflection.AssemblyName]::new($EventArgs.Name).Name if ([string]::IsNullOrWhiteSpace($PowerForgeRequestedAssemblyName)) { return $null } # AssemblyName.Name is expected to be a simple name. Enforce that # contract before using it as a path segment, then verify the # canonical candidate remains beneath the private assembly root. if ($PowerForgeRequestedAssemblyName -ne [IO.Path]::GetFileName($PowerForgeRequestedAssemblyName) -or $PowerForgeRequestedAssemblyName.IndexOfAny([IO.Path]::GetInvalidFileNameChars()) -ge 0) { return $null } foreach ($PowerForgeDesktopAssemblyRoot in $PowerForgeDesktopAssemblyRoots) { $PowerForgeAssemblyCandidate = [IO.Path]::GetFullPath( [IO.Path]::Combine($PowerForgeDesktopAssemblyRoot, $PowerForgeRequestedAssemblyName + '.dll')) $PowerForgeDesktopAssemblyRootPrefix = [IO.Path]::GetFullPath($PowerForgeDesktopAssemblyRoot) if (-not $PowerForgeDesktopAssemblyRootPrefix.EndsWith([IO.Path]::DirectorySeparatorChar.ToString(), [StringComparison]::Ordinal)) { $PowerForgeDesktopAssemblyRootPrefix += [IO.Path]::DirectorySeparatorChar } if (-not $PowerForgeAssemblyCandidate.StartsWith($PowerForgeDesktopAssemblyRootPrefix, [StringComparison]::OrdinalIgnoreCase)) { continue } if ([IO.File]::Exists($PowerForgeAssemblyCandidate)) { return [Reflection.Assembly]::LoadFrom($PowerForgeAssemblyCandidate) } } return $null } catch { return $null } }.GetNewClosure() [AppDomain]::CurrentDomain.add_AssemblyResolve($PowerForgeDesktopAssemblyResolver) $PowerForgeDesktopAssemblyResolverState.Registered = $true $PowerForgeResolverForRemoval = $PowerForgeDesktopAssemblyResolver $UnregisterPowerForgeDesktopAssemblyResolver = { if ($PowerForgeDesktopAssemblyResolverState.Registered) { [AppDomain]::CurrentDomain.remove_AssemblyResolve($PowerForgeResolverForRemoval) $PowerForgeDesktopAssemblyResolverState.Registered = $false } }.GetNewClosure() $PowerForgePreviousOnRemove = $ExecutionContext.SessionState.Module.OnRemove $ExecutionContext.SessionState.Module.OnRemove = { try { if ($null -ne $UnregisterPowerForgeDesktopAssemblyResolver) { & $UnregisterPowerForgeDesktopAssemblyResolver } } finally { if ($null -ne $PowerForgePreviousOnRemove) { & $PowerForgePreviousOnRemove @args } } }.GetNewClosure() } if ($PSEdition -ne 'Core') { $LibrariesScript = [IO.Path]::Combine($PowerForgeModuleRoot, 'HomeAssistantX.Libraries.ps1') if (Test-Path -LiteralPath $LibrariesScript) { try { . $LibrariesScript } catch { if ($null -ne $UnregisterPowerForgeDesktopAssemblyResolver) { & $UnregisterPowerForgeDesktopAssemblyResolver } throw } } } $PowerForgeDesktopBinaryLoaded = $false $PowerForgeDesktopBinaryDirectories = @() try { $ImportModule = Get-Command -Name Import-Module -Module Microsoft.PowerShell.Core $PowerForgeResolvedBinaryModules = [Collections.Generic.List[object]]::new() $PowerForgeResolvedBinaryModulePaths = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($Library in $LibraryFileNames) { try { $ResolvedModuleAssembly = & $ResolvePowerForgeModuleAssembly -LibraryFileName $Library $ResolvedModuleAssemblyPath = [IO.Path]::GetFullPath($ResolvedModuleAssembly.Path) if ($PowerForgeResolvedBinaryModulePaths.Add($ResolvedModuleAssemblyPath)) { $PowerForgeResolvedBinaryModules.Add([pscustomobject]@{ Library = $Library Assembly = $ResolvedModuleAssembly }) } } catch { if ($ErrorActionPreference -eq 'Stop') { if ($null -ne $UnregisterPowerForgeDesktopAssemblyResolver) { & $UnregisterPowerForgeDesktopAssemblyResolver } throw } else { Write-Warning -Message "Resolving module $Library failed. Fix errors before continuing. Error: $($_.Exception.Message)" } } } $PowerForgeCoreModuleAssemblyPaths = [string[]]@($PowerForgeResolvedBinaryModules.Assembly.Path) if ($PSEdition -eq 'Core' -and $PowerForgeResolvedBinaryModules.Count -gt 0) { $LoaderAssemblyPath = [IO.Path]::Combine($PowerForgeResolvedBinaryModules[0].Assembly.Directory, 'HomeAssistantX.ModuleLoadContext.dll') if (-not ('HomeAssistantX.ModuleLoadContext.ModuleAssemblyLoadContext' -as [type])) { Add-Type -Path $LoaderAssemblyPath -ErrorAction Stop } } for ($LibraryIndex = 0; $LibraryIndex -lt $PowerForgeResolvedBinaryModules.Count; $LibraryIndex++) { $Library = $PowerForgeResolvedBinaryModules[$LibraryIndex].Library $ResolvedModuleAssembly = $PowerForgeResolvedBinaryModules[$LibraryIndex].Assembly $ModuleAssemblyPath = $ResolvedModuleAssembly.Path $LibraryDirectory = $ResolvedModuleAssembly.Directory $LibFolder = $LibraryDirectory $LibraryName = [IO.Path]::GetFileNameWithoutExtension($ModuleAssemblyPath) $Class = "$LibraryName.Initialize" try { if ($PSEdition -eq 'Core') { $ModuleAssembly = [HomeAssistantX.ModuleLoadContext.ModuleAssemblyLoadContext]::LoadModuleFromGroup( $PowerForgeCoreModuleAssemblyPaths, $ModuleAssemblyPath, 'HomeAssistantX') $InnerModule = & $ImportModule -Assembly $ModuleAssembly -Force -PassThru -ErrorAction Stop # Type accelerator registration relies on $ModuleAssembly and $LibFolder from this ALC loader scope. $RegisterPowerForgeAssemblyTypeAccelerators = { param( [Parameter(Mandatory = $true)][System.Reflection.Assembly] $ModuleAssembly, [Parameter(Mandatory = $true)][string] $LibFolder ) $Mode = 'AllowList' $RequestedTypes = @('HomeAssistantX.PowerShell.HomeAssistantConnection', 'HomeAssistantX.PowerShell.HomeAssistantLogLine', 'HomeAssistantX.PowerShell.HomeAssistantAppAction', 'HomeAssistantX.Inventory.HomeAssistantEntityInfo', 'HomeAssistantX.Services.HomeAssistantActionDefinition', 'HomeAssistantX.Controls.HomeAssistantPowerAction', 'HomeAssistantX.Controls.HomeAssistantCoverAction', 'HomeAssistantX.Controls.HomeAssistantMediaPlaybackAction', 'HomeAssistantX.Controls.HomeAssistantMediaVolumeStepAction', 'HomeAssistantX.Controls.HomeAssistantMediaRepeatMode', 'HomeAssistantX.Controls.HomeAssistantMediaEnqueueMode', 'HomeAssistantX.Controls.HomeAssistantRemoteCommandType', 'HomeAssistantX.PowerShell.HomeAssistantRemoteAction', 'HomeAssistantX.Controls.HomeAssistantLockAction', 'HomeAssistantX.Controls.HomeAssistantAlarmAction', 'HomeAssistantX.Controls.HomeAssistantButtonDomain', 'HomeAssistantX.Controls.HomeAssistantFanAction', 'HomeAssistantX.Controls.HomeAssistantFanDirection', 'HomeAssistantX.Controls.HomeAssistantHelperDomain', 'HomeAssistantX.Controls.HomeAssistantHumidifierAction', 'HomeAssistantX.Controls.HomeAssistantLawnMowerAction', 'HomeAssistantX.Controls.HomeAssistantSirenAction', 'HomeAssistantX.Controls.HomeAssistantVacuumAction', 'HomeAssistantX.Controls.HomeAssistantValveAction', 'HomeAssistantX.Controls.HomeAssistantWaterHeaterAction', 'HomeAssistantX.PowerShell.HomeAssistantRoutineAction', 'HomeAssistantX.Authentication.IHomeAssistantAccessTokenProvider', 'HomeAssistantX.Authentication.StaticAccessTokenProvider', 'HomeAssistantX.Operations.HomeAssistantCapabilityAvailability', 'HomeAssistantX.Energy.HomeAssistantEnergyPeriod', 'HomeAssistantX.Recorder.HomeAssistantStatisticKind', 'HomeAssistantX.Recorder.HomeAssistantStatisticPeriod', 'HomeAssistantX.Recorder.HomeAssistantStatisticType', 'HomeAssistantX.Recorder.HomeAssistantStatisticMeanType', 'HomeAssistantX.Recorder.HomeAssistantStatisticImportMetadata', 'HomeAssistantX.Recorder.HomeAssistantStatisticImportRow', 'HomeAssistantX.Weather.HomeAssistantWeatherForecastType', 'HomeAssistantX.Weather.HomeAssistantWeatherFeature', 'HomeAssistantX.Cameras.HomeAssistantCameraFeature', 'HomeAssistantX.Cameras.HomeAssistantCameraOrientation', 'HomeAssistantX.Dashboards.HomeAssistantDashboardResourceType') $RequestedAssemblies = @() if ($null -eq $ModuleAssembly) { Write-Warning -Message 'Module assembly was not available. ALC dependency type exposure is disabled.' return } if ([string]::IsNullOrWhiteSpace($LibFolder)) { Write-Warning -Message 'Module library folder was not available. ALC dependency type exposure is disabled.' return } $PowerForgeAlcLibraryDirectory = $null if ([IO.Path]::IsPathRooted($LibFolder)) { $PowerForgeAlcLibraryDirectory = [IO.Path]::GetFullPath($LibFolder) } elseif ($LibFolder.Contains('..') -or $LibFolder.IndexOfAny([IO.Path]::GetInvalidFileNameChars()) -ge 0) { Write-Warning -Message "Module library folder '$LibFolder' must be a simple folder name or a rooted development binary directory. ALC dependency type exposure is disabled." return } else { $PowerForgeAlcLibraryDirectory = [IO.Path]::Combine($PSScriptRoot, 'Lib', $LibFolder) } if ($Mode -eq 'AllowList' -and $RequestedTypes.Count -eq 0) { Write-Warning -Message 'AllowList type accelerator mode was configured without type names. No ALC dependency type accelerators will be registered.' return } if (($Mode -eq 'Assembly' -or $Mode -eq 'Enums') -and $RequestedAssemblies.Count -eq 0) { if ($RequestedTypes.Count -eq 0) { Write-Warning -Message "$Mode type accelerator mode was configured without assembly names or type names. No ALC dependency type accelerators will be registered." return } Write-Warning -Message "$Mode type accelerator mode was configured without assembly names. Only explicitly configured type names will be registered." } $TypeAccelerators = [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators') if ($null -eq $TypeAccelerators) { Write-Warning -Message 'PowerShell type accelerator APIs are not available. ALC dependency type exposure is disabled.' return } $AddTypeAccelerator = $TypeAccelerators.GetMethod('Add', [type[]]@([string], [type])) $GetTypeAccelerators = $TypeAccelerators.GetProperty('Get', [System.Reflection.BindingFlags] 'Static,Public,NonPublic') if ($null -eq $AddTypeAccelerator -or $null -eq $GetTypeAccelerators) { Write-Warning -Message 'PowerShell type accelerator APIs are incomplete. ALC dependency type exposure is disabled.' return } $ModuleAlc = [System.Runtime.Loader.AssemblyLoadContext]::GetLoadContext($ModuleAssembly) if ($null -eq $ModuleAlc) { Write-Warning -Message 'Unable to resolve the module AssemblyLoadContext. ALC dependency type exposure is disabled.' return } if ($null -eq $script:PowerForgeRegisteredAssemblyTypeAccelerators) { $script:PowerForgeRegisteredAssemblyTypeAccelerators = @{} } $ImportPowerForgeAlcAssembly = { param([Parameter(Mandatory = $true)][string] $AssemblyName) foreach ($Assembly in $ModuleAlc.Assemblies) { if ($Assembly.GetName().Name -eq $AssemblyName) { return $Assembly } } try { return $ModuleAlc.LoadFromAssemblyName([System.Reflection.AssemblyName]::new($AssemblyName)) } catch { $AssemblyPath = [IO.Path]::Combine($PowerForgeAlcLibraryDirectory, $AssemblyName + '.dll') if (Test-Path -LiteralPath $AssemblyPath) { try { $AssemblyNameObject = [System.Reflection.AssemblyName]::GetAssemblyName($AssemblyPath) return $ModuleAlc.LoadFromAssemblyName($AssemblyNameObject) } catch { Write-Warning -Message "Could not load ALC assembly '$AssemblyName' for type accelerator exposure: $($_.Exception.Message)" } } } return $null } $FindPowerForgeAlcType = { param([Parameter(Mandatory = $true)][string] $TypeName) foreach ($Assembly in $ModuleAlc.Assemblies) { $Type = $Assembly.GetType($TypeName, $false, $false) if ($null -ne $Type) { return $Type } } $LibDirectory = $PowerForgeAlcLibraryDirectory if (-not (Test-Path -LiteralPath $LibDirectory)) { return $null } foreach ($File in Get-ChildItem -LiteralPath $LibDirectory -File -ErrorAction SilentlyContinue | Where-Object Extension -IEQ '.dll') { try { $AssemblyName = [System.Reflection.AssemblyName]::GetAssemblyName($File.FullName) $Assembly = & $ImportPowerForgeAlcAssembly -AssemblyName $AssemblyName.Name if ($null -eq $Assembly) { continue } $Type = $Assembly.GetType($TypeName, $false, $false) if ($null -ne $Type) { return $Type } } catch { continue } } return $null } $AddPowerForgeTypeAccelerator = { param([Parameter(Mandatory = $true)][type] $Type) if ([string]::IsNullOrWhiteSpace($Type.FullName)) { return } $Name = $Type.FullName $Existing = $GetTypeAccelerators.GetValue($null) if ($Existing.ContainsKey($Name)) { $ExistingType = $Existing[$Name] if ([object]::ReferenceEquals($ExistingType, $Type)) { return } else { $ExistingAssemblyName = $ExistingType.Assembly.GetName() $TypeAssemblyName = $Type.Assembly.GetName() $ExistingLoadContext = [System.Runtime.Loader.AssemblyLoadContext]::GetLoadContext($ExistingType.Assembly) $TypeLoadContext = [System.Runtime.Loader.AssemblyLoadContext]::GetLoadContext($Type.Assembly) if ([object]::ReferenceEquals($ExistingLoadContext, $TypeLoadContext) -and [object]::Equals($ExistingAssemblyName.FullName, $TypeAssemblyName.FullName)) { Write-Verbose -Message "Type accelerator '$Name' already exists in the same AssemblyLoadContext from the same assembly identity. Keeping the existing accelerator and skipping the duplicate type from $($TypeAssemblyName.Name)." } else { Write-Warning -Message "Type accelerator '$Name' already exists from $($ExistingAssemblyName.FullName). Keeping the existing accelerator and skipping the ALC type from $($TypeAssemblyName.FullName)." } } return } try { $AddTypeAccelerator.Invoke($null, @($Name, $Type)) | Out-Null } catch { Write-Warning -Message "Type accelerator '$Name' could not be registered from $($Type.Assembly.GetName().Name): $($_.Exception.Message)" return } $script:PowerForgeRegisteredAssemblyTypeAccelerators[$Name] = $Type } if ($Mode -eq 'Assembly' -or $Mode -eq 'Enums') { foreach ($AssemblyName in $RequestedAssemblies) { $Assembly = & $ImportPowerForgeAlcAssembly -AssemblyName $AssemblyName if ($null -eq $Assembly) { Write-Warning -Message "Assembly '$AssemblyName' was not found in the module AssemblyLoadContext. No type accelerators were registered for it." continue } try { $ExportedTypes = @($Assembly.GetExportedTypes()) } catch { Write-Warning -Message "Could not enumerate exported types from assembly '$AssemblyName' for type accelerator exposure: $($_.Exception.Message)" continue } foreach ($Type in $ExportedTypes) { if ($Mode -eq 'Enums' -and -not $Type.IsEnum) { continue } & $AddPowerForgeTypeAccelerator -Type $Type } } } foreach ($TypeName in $RequestedTypes) { $Type = & $FindPowerForgeAlcType -TypeName $TypeName if ($null -eq $Type) { Write-Warning -Message "Type '$TypeName' was not found in the module AssemblyLoadContext. No type accelerator was registered." continue } & $AddPowerForgeTypeAccelerator -Type $Type } if ($script:PowerForgeAssemblyTypeAcceleratorCleanupRegistered -ne $true) { $script:PowerForgeAssemblyTypeAcceleratorCleanupRegistered = $true $RegisteredPowerForgeTypeAccelerators = $script:PowerForgeRegisteredAssemblyTypeAccelerators $PreviousPowerForgeOnRemove = $ExecutionContext.SessionState.Module.OnRemove $ExecutionContext.SessionState.Module.OnRemove = { try { $TypeAccelerators = [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators') if ($null -eq $TypeAccelerators -or $null -eq $RegisteredPowerForgeTypeAccelerators) { return } $GetTypeAccelerators = $TypeAccelerators.GetProperty('Get', [System.Reflection.BindingFlags] 'Static,Public,NonPublic') $RemoveTypeAccelerator = $TypeAccelerators.GetMethod('Remove', [type[]]@([string])) if ($null -eq $GetTypeAccelerators -or $null -eq $RemoveTypeAccelerator) { return } $Existing = $GetTypeAccelerators.GetValue($null) foreach ($Entry in @($RegisteredPowerForgeTypeAccelerators.GetEnumerator())) { if ($Existing.ContainsKey($Entry.Key) -and [object]::ReferenceEquals($Existing[$Entry.Key], $Entry.Value)) { $RemoveTypeAccelerator.Invoke($null, @($Entry.Key)) | Out-Null } } } finally { if ($null -ne $PreviousPowerForgeOnRemove) { & $PreviousPowerForgeOnRemove @args } } }.GetNewClosure() } } # Type accelerator exposure is PowerShell Core-only because it depends on AssemblyLoadContext. try { & $RegisterPowerForgeAssemblyTypeAccelerators -ModuleAssembly $ModuleAssembly -LibFolder $LibFolder } catch { Write-Warning -Message "ALC type accelerator registration failed: $($_.Exception.Message)" } if ($InnerModule) { # Import-Module -Assembly loads the inner binary module into its own module object. PowerShell has no # public API to copy those exported cmdlets back to the script-module wrapper, so this uses the same # private PSModuleInfo hook used by community ALC loaders. $AddExportedCmdlet = [System.Management.Automation.PSModuleInfo].GetMethod( 'AddExportedCmdlet', [System.Reflection.BindingFlags]'Instance, NonPublic' ) $PowerForgeOuterModule = $ExecutionContext.SessionState.Module $PowerForgeIsModuleWrapper = $false if ($null -ne $PowerForgeOuterModule -and -not [string]::IsNullOrWhiteSpace($PowerForgeOuterModule.Path) -and -not [string]::IsNullOrWhiteSpace($PSCommandPath)) { try { [StringComparison] $PowerForgePathComparison = & { param([string] $PowerForgeProbePath) # Case behavior belongs to the containing filesystem. Windows can opt individual # directories into case sensitivity, while macOS volumes may be case-insensitive. # Inspect existing names without requiring write access; ambiguity fails closed. try { $PowerForgeProbeDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($PowerForgeProbePath)) $PowerForgeNames = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::Ordinal) foreach ($PowerForgeEntry in [IO.Directory]::EnumerateFileSystemEntries($PowerForgeProbeDirectory)) { [void] $PowerForgeNames.Add([IO.Path]::GetFileName($PowerForgeEntry)) } foreach ($PowerForgeName in $PowerForgeNames) { foreach ($PowerForgeAlternateName in @($PowerForgeName.ToUpperInvariant(), $PowerForgeName.ToLowerInvariant())) { if ([string]::Equals($PowerForgeName, $PowerForgeAlternateName, [StringComparison]::Ordinal) -or $PowerForgeNames.Contains($PowerForgeAlternateName)) { continue } $PowerForgeAlternatePath = [IO.Path]::Combine($PowerForgeProbeDirectory, $PowerForgeAlternateName) if ([IO.File]::Exists($PowerForgeAlternatePath) -or [IO.Directory]::Exists($PowerForgeAlternatePath)) { return [StringComparison]::OrdinalIgnoreCase } return [StringComparison]::Ordinal } } } catch { # Ordinal comparison prevents an uncertain probe from exporting into a caller. } return [StringComparison]::Ordinal } $PSCommandPath $PowerForgeIsModuleWrapper = [string]::Equals( [IO.Path]::GetFullPath($PowerForgeOuterModule.Path), [IO.Path]::GetFullPath($PSCommandPath), $PowerForgePathComparison ) } catch { $PowerForgeIsModuleWrapper = $false } } if (-not $PowerForgeIsModuleWrapper) { # A Script/ScriptPacked entry point, including one dot-sourced by another module, must not mutate # that caller's export table. The imported inner module's commands remain available to this script. } elseif ($null -ne $AddExportedCmdlet) { foreach ($Cmd in $InnerModule.ExportedCmdlets.Values) { $AddExportedCmdlet.Invoke($PowerForgeOuterModule, @(, $Cmd)) | Out-Null } $AddExportedAlias = [System.Management.Automation.PSModuleInfo].GetMethod( 'AddExportedAlias', [System.Reflection.BindingFlags]'Instance, NonPublic' ) if ($null -ne $AddExportedAlias) { foreach ($Alias in $InnerModule.ExportedAliases.Values) { $AliasTarget = if ([string]::IsNullOrWhiteSpace($Alias.Definition)) { $Alias.ResolvedCommandName } else { $Alias.Definition } try { # The alias must exist in this module scope before the private export table can reference it. Set-Alias -Name $Alias.Name -Value $AliasTarget -Scope Local -Force -ErrorAction Stop $ExportedAlias = $ExecutionContext.SessionState.InvokeCommand.GetCommand($Alias.Name, [System.Management.Automation.CommandTypes]::Alias) if ($null -ne $ExportedAlias) { $AddExportedAlias.Invoke($PowerForgeOuterModule, @(, $ExportedAlias)) | Out-Null } else { Write-Warning -Message "Alias '$($Alias.Name)' from $LibraryName was created but could not be resolved for export." } } catch { Write-Warning -Message "Alias '$($Alias.Name)' from $LibraryName could not be re-exported: $($_.Exception.Message)" } } } else { Write-Warning -Message "AddExportedAlias is not available on this PowerShell version. Aliases from $LibraryName will not be re-exported to the module scope." } } else { Write-Warning -Message "AddExportedCmdlet is not available on this PowerShell version. Falling back to direct Import-Module; cmdlets from $LibraryName will load from the default context." & $ImportModule $ModuleAssemblyPath -ErrorAction Stop } } } elseif (-not ($Class -as [type])) { & $ImportModule $ModuleAssemblyPath -ErrorAction Stop } else { $Type = "$Class" -as [Type] & $ImportModule -Force -Assembly ($Type.Assembly) } if ($PSEdition -ne 'Core') { $PowerForgeDesktopBinaryLoaded = $true if ($LibraryDirectory -notin $PowerForgeDesktopBinaryDirectories) { $PowerForgeDesktopBinaryDirectories += $LibraryDirectory } } } catch { if ($ErrorActionPreference -eq 'Stop') { if ($null -ne $UnregisterPowerForgeDesktopAssemblyResolver) { & $UnregisterPowerForgeDesktopAssemblyResolver } throw } else { Write-Warning -Message "Importing module $Library failed. Fix errors before continuing. Error: $($_.Exception.Message)" } } } } catch { if ($ErrorActionPreference -eq 'Stop') { if ($null -ne $UnregisterPowerForgeDesktopAssemblyResolver) { & $UnregisterPowerForgeDesktopAssemblyResolver } throw } else { Write-Warning -Message "Importing module $Library failed. Fix errors before continuing. Error: $($_.Exception.Message)" } } if ($PSEdition -ne 'Core' -and $PowerForgeDesktopBinaryLoaded) { foreach ($PowerForgeDesktopBinaryDirectory in $PowerForgeDesktopBinaryDirectories) { if ($PSEdition -ne 'Core') { # Desktop loads module dependencies into the default AppDomain, so expose the same configured scripting types as Core. $RegisterPowerForgeDesktopAssemblyTypeAccelerators = { param([Parameter(Mandatory = $true)][string] $LibraryDirectory) $Mode = 'AllowList' $RequestedTypes = @('HomeAssistantX.PowerShell.HomeAssistantConnection', 'HomeAssistantX.PowerShell.HomeAssistantLogLine', 'HomeAssistantX.PowerShell.HomeAssistantAppAction', 'HomeAssistantX.Inventory.HomeAssistantEntityInfo', 'HomeAssistantX.Services.HomeAssistantActionDefinition', 'HomeAssistantX.Controls.HomeAssistantPowerAction', 'HomeAssistantX.Controls.HomeAssistantCoverAction', 'HomeAssistantX.Controls.HomeAssistantMediaPlaybackAction', 'HomeAssistantX.Controls.HomeAssistantMediaVolumeStepAction', 'HomeAssistantX.Controls.HomeAssistantMediaRepeatMode', 'HomeAssistantX.Controls.HomeAssistantMediaEnqueueMode', 'HomeAssistantX.Controls.HomeAssistantRemoteCommandType', 'HomeAssistantX.PowerShell.HomeAssistantRemoteAction', 'HomeAssistantX.Controls.HomeAssistantLockAction', 'HomeAssistantX.Controls.HomeAssistantAlarmAction', 'HomeAssistantX.Controls.HomeAssistantButtonDomain', 'HomeAssistantX.Controls.HomeAssistantFanAction', 'HomeAssistantX.Controls.HomeAssistantFanDirection', 'HomeAssistantX.Controls.HomeAssistantHelperDomain', 'HomeAssistantX.Controls.HomeAssistantHumidifierAction', 'HomeAssistantX.Controls.HomeAssistantLawnMowerAction', 'HomeAssistantX.Controls.HomeAssistantSirenAction', 'HomeAssistantX.Controls.HomeAssistantVacuumAction', 'HomeAssistantX.Controls.HomeAssistantValveAction', 'HomeAssistantX.Controls.HomeAssistantWaterHeaterAction', 'HomeAssistantX.PowerShell.HomeAssistantRoutineAction', 'HomeAssistantX.Authentication.IHomeAssistantAccessTokenProvider', 'HomeAssistantX.Authentication.StaticAccessTokenProvider', 'HomeAssistantX.Operations.HomeAssistantCapabilityAvailability', 'HomeAssistantX.Energy.HomeAssistantEnergyPeriod', 'HomeAssistantX.Recorder.HomeAssistantStatisticKind', 'HomeAssistantX.Recorder.HomeAssistantStatisticPeriod', 'HomeAssistantX.Recorder.HomeAssistantStatisticType', 'HomeAssistantX.Recorder.HomeAssistantStatisticMeanType', 'HomeAssistantX.Recorder.HomeAssistantStatisticImportMetadata', 'HomeAssistantX.Recorder.HomeAssistantStatisticImportRow', 'HomeAssistantX.Weather.HomeAssistantWeatherForecastType', 'HomeAssistantX.Weather.HomeAssistantWeatherFeature', 'HomeAssistantX.Cameras.HomeAssistantCameraFeature', 'HomeAssistantX.Cameras.HomeAssistantCameraOrientation', 'HomeAssistantX.Dashboards.HomeAssistantDashboardResourceType') $RequestedAssemblies = @() $IgnoredLibraryFileNames = @() if ([string]::IsNullOrWhiteSpace($LibraryDirectory) -or -not (Test-Path -LiteralPath $LibraryDirectory)) { Write-Warning -Message 'Module library directory was not available. Desktop dependency type exposure is disabled.' return } if ($Mode -eq 'AllowList' -and $RequestedTypes.Count -eq 0) { Write-Warning -Message 'AllowList type accelerator mode was configured without type names. No Desktop dependency type accelerators will be registered.' return } if (($Mode -eq 'Assembly' -or $Mode -eq 'Enums') -and $RequestedAssemblies.Count -eq 0) { if ($RequestedTypes.Count -eq 0) { Write-Warning -Message "$Mode type accelerator mode was configured without assembly names or type names. No Desktop dependency type accelerators will be registered." return } Write-Warning -Message "$Mode type accelerator mode was configured without assembly names. Only explicitly configured type names will be registered." } $TypeAccelerators = [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators') if ($null -eq $TypeAccelerators) { Write-Warning -Message 'PowerShell type accelerator APIs are not available. Desktop dependency type exposure is disabled.' return } $AddTypeAccelerator = $TypeAccelerators.GetMethod('Add', [type[]]@([string], [type])) $GetTypeAccelerators = $TypeAccelerators.GetProperty('Get', [System.Reflection.BindingFlags] 'Static,Public,NonPublic') if ($null -eq $AddTypeAccelerator -or $null -eq $GetTypeAccelerators) { Write-Warning -Message 'PowerShell type accelerator APIs are incomplete. Desktop dependency type exposure is disabled.' return } if ($null -eq $script:PowerForgeRegisteredAssemblyTypeAccelerators) { $script:PowerForgeRegisteredAssemblyTypeAccelerators = @{} } $ResolvedPowerForgeDesktopAssemblies = @{} $FailedPowerForgeDesktopAssemblies = @{} $LibraryDirectoryFullPath = [IO.Path]::GetFullPath($LibraryDirectory) $LibraryDirectoryPrefix = $LibraryDirectoryFullPath.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar $TestPowerForgeDesktopIgnoredAssembly = { param([Parameter(Mandatory = $true)] $Assembly) return $IgnoredLibraryFileNames -contains ($Assembly.GetName().Name + '.dll') } $TestPowerForgeDesktopModuleAssembly = { param([Parameter(Mandatory = $true)] $Assembly) if ($Assembly.IsDynamic) { return $false } try { if ([string]::IsNullOrWhiteSpace($Assembly.Location)) { return $false } $AssemblyFullPath = [IO.Path]::GetFullPath($Assembly.Location) return $AssemblyFullPath.StartsWith($LibraryDirectoryPrefix, [StringComparison]::OrdinalIgnoreCase) } catch { return $false } } $TestPowerForgeDesktopAssemblyContentMatch = { param( [Parameter(Mandatory = $true)] $Assembly, [Parameter(Mandatory = $true)][string] $ExpectedPath ) if ($Assembly.IsDynamic -or [string]::IsNullOrWhiteSpace($Assembly.Location) -or -not (Test-Path -LiteralPath $ExpectedPath)) { return $false } try { $ExpectedName = [Reflection.AssemblyName]::GetAssemblyName($ExpectedPath) if ($Assembly.GetName().FullName -ne $ExpectedName.FullName) { return $false } $LoadedPath = [IO.Path]::GetFullPath($Assembly.Location) $ExpectedFullPath = [IO.Path]::GetFullPath($ExpectedPath) if ([string]::Equals($LoadedPath, $ExpectedFullPath, [StringComparison]::OrdinalIgnoreCase)) { return $true } $ComputePowerForgeDesktopAssemblyHash = { param([Parameter(Mandatory = $true)][string] $Path) $Stream = $null $Hasher = $null try { $Stream = [IO.File]::OpenRead($Path) $Hasher = [Security.Cryptography.SHA256]::Create() return [Convert]::ToBase64String($Hasher.ComputeHash($Stream)) } finally { if ($null -ne $Hasher) { $Hasher.Dispose() } if ($null -ne $Stream) { $Stream.Dispose() } } } $LoadedHash = & $ComputePowerForgeDesktopAssemblyHash -Path $LoadedPath $ExpectedHash = & $ComputePowerForgeDesktopAssemblyHash -Path $ExpectedFullPath return $LoadedHash -eq $ExpectedHash } catch { return $false } } $ImportPowerForgeDesktopAssembly = { param([Parameter(Mandatory = $true)][string] $AssemblyName) $SimpleName = if ($AssemblyName.EndsWith('.dll', [StringComparison]::OrdinalIgnoreCase)) { [IO.Path]::GetFileNameWithoutExtension($AssemblyName) } else { $AssemblyName } $AssemblyFileName = $SimpleName + '.dll' if ($IgnoredLibraryFileNames -contains $AssemblyFileName) { $FailedPowerForgeDesktopAssemblies[$SimpleName] = $true return $null } if ($ResolvedPowerForgeDesktopAssemblies.ContainsKey($SimpleName)) { return $ResolvedPowerForgeDesktopAssemblies[$SimpleName] } if ($FailedPowerForgeDesktopAssemblies.ContainsKey($SimpleName)) { return $null } $AssemblyPath = [IO.Path]::Combine($LibraryDirectory, $AssemblyFileName) foreach ($Assembly in [AppDomain]::CurrentDomain.GetAssemblies()) { if ($Assembly.GetName().Name -eq $SimpleName) { if ((& $TestPowerForgeDesktopModuleAssembly -Assembly $Assembly) -or (& $TestPowerForgeDesktopAssemblyContentMatch -Assembly $Assembly -ExpectedPath $AssemblyPath)) { $ResolvedPowerForgeDesktopAssemblies[$SimpleName] = $Assembly return $Assembly } } } if (Test-Path -LiteralPath $AssemblyPath) { try { $LoadedAssembly = [System.Reflection.Assembly]::LoadFrom($AssemblyPath) if ((& $TestPowerForgeDesktopModuleAssembly -Assembly $LoadedAssembly) -or (& $TestPowerForgeDesktopAssemblyContentMatch -Assembly $LoadedAssembly -ExpectedPath $AssemblyPath)) { $ResolvedPowerForgeDesktopAssemblies[$SimpleName] = $LoadedAssembly return $LoadedAssembly } $FailedPowerForgeDesktopAssemblies[$SimpleName] = $true Write-Warning -Message "Desktop assembly '$SimpleName' resolved outside the module library directory. Skipping type accelerator exposure to avoid registering the wrong assembly version." return $null } catch { $FailedPowerForgeDesktopAssemblies[$SimpleName] = $true Write-Warning -Message "Could not load Desktop assembly '$SimpleName' for type accelerator exposure: $($_.Exception.Message)" return $null } } foreach ($Assembly in [AppDomain]::CurrentDomain.GetAssemblies()) { if ($Assembly.GetName().Name -eq $SimpleName) { $ResolvedPowerForgeDesktopAssemblies[$SimpleName] = $Assembly return $Assembly } } $FailedPowerForgeDesktopAssemblies[$SimpleName] = $true return $null } $FindPowerForgeDesktopType = { param([Parameter(Mandatory = $true)][string] $TypeName) foreach ($Assembly in [AppDomain]::CurrentDomain.GetAssemblies()) { if ((& $TestPowerForgeDesktopIgnoredAssembly -Assembly $Assembly) -or -not (& $TestPowerForgeDesktopModuleAssembly -Assembly $Assembly)) { continue } $Type = $Assembly.GetType($TypeName, $false, $false) if ($null -ne $Type) { return $Type } } foreach ($File in Get-ChildItem -LiteralPath $LibraryDirectory -File -ErrorAction SilentlyContinue | Where-Object Extension -IEQ '.dll') { if ($IgnoredLibraryFileNames -contains $File.Name) { continue } $Assembly = & $ImportPowerForgeDesktopAssembly -AssemblyName $File.BaseName if ($null -eq $Assembly) { continue } $Type = $Assembly.GetType($TypeName, $false, $false) if ($null -ne $Type) { return $Type } } foreach ($Assembly in [AppDomain]::CurrentDomain.GetAssemblies()) { if (& $TestPowerForgeDesktopIgnoredAssembly -Assembly $Assembly) { continue } $AssemblyFileName = $Assembly.GetName().Name + '.dll' if (Test-Path -LiteralPath ([IO.Path]::Combine($LibraryDirectory, $AssemblyFileName))) { # A module-owned assembly with this identity exists but could not be selected above. Do not # silently fall back to a globally loaded copy with a different location or version. continue } $Type = $Assembly.GetType($TypeName, $false, $false) if ($null -ne $Type) { return $Type } } return $null } $AddPowerForgeDesktopTypeAccelerator = { param([Parameter(Mandatory = $true)][type] $Type) if ([string]::IsNullOrWhiteSpace($Type.FullName)) { return } $Name = $Type.FullName $Existing = $GetTypeAccelerators.GetValue($null) if ($Existing.ContainsKey($Name)) { $ExistingType = $Existing[$Name] if ([object]::ReferenceEquals($ExistingType, $Type)) { return } Write-Warning -Message "Type accelerator '$Name' already exists from $($ExistingType.Assembly.GetName().FullName). Keeping it and skipping the Desktop type from $($Type.Assembly.GetName().FullName)." return } try { $AddTypeAccelerator.Invoke($null, @($Name, $Type)) | Out-Null } catch { Write-Warning -Message "Type accelerator '$Name' could not be registered from $($Type.Assembly.GetName().Name): $($_.Exception.Message)" return } $script:PowerForgeRegisteredAssemblyTypeAccelerators[$Name] = $Type } if ($Mode -eq 'Assembly' -or $Mode -eq 'Enums') { foreach ($AssemblyName in $RequestedAssemblies) { $Assembly = & $ImportPowerForgeDesktopAssembly -AssemblyName $AssemblyName if ($null -eq $Assembly) { Write-Warning -Message "Assembly '$AssemblyName' was not found in the module library directory. No Desktop type accelerators were registered for it." continue } try { $ExportedTypes = @($Assembly.GetExportedTypes()) } catch { Write-Warning -Message "Could not enumerate exported types from Desktop assembly '$AssemblyName' for type accelerator exposure: $($_.Exception.Message)" continue } foreach ($Type in $ExportedTypes) { if ($Mode -eq 'Enums' -and -not $Type.IsEnum) { continue } & $AddPowerForgeDesktopTypeAccelerator -Type $Type } } } foreach ($TypeName in $RequestedTypes) { $Type = & $FindPowerForgeDesktopType -TypeName $TypeName if ($null -eq $Type) { Write-Warning -Message "Type '$TypeName' was not found in the module AppDomain. No Desktop type accelerator was registered." continue } & $AddPowerForgeDesktopTypeAccelerator -Type $Type } if ($script:PowerForgeAssemblyTypeAcceleratorCleanupRegistered -ne $true) { $script:PowerForgeAssemblyTypeAcceleratorCleanupRegistered = $true $RegisteredPowerForgeTypeAccelerators = $script:PowerForgeRegisteredAssemblyTypeAccelerators $PreviousPowerForgeOnRemove = $ExecutionContext.SessionState.Module.OnRemove $ExecutionContext.SessionState.Module.OnRemove = { try { $TypeAccelerators = [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators') if ($null -eq $TypeAccelerators -or $null -eq $RegisteredPowerForgeTypeAccelerators) { return } $GetTypeAccelerators = $TypeAccelerators.GetProperty('Get', [System.Reflection.BindingFlags] 'Static,Public,NonPublic') $RemoveTypeAccelerator = $TypeAccelerators.GetMethod('Remove', [type[]]@([string])) if ($null -eq $GetTypeAccelerators -or $null -eq $RemoveTypeAccelerator) { return } $Existing = $GetTypeAccelerators.GetValue($null) foreach ($Entry in @($RegisteredPowerForgeTypeAccelerators.GetEnumerator())) { if ($Existing.ContainsKey($Entry.Key) -and [object]::ReferenceEquals($Existing[$Entry.Key], $Entry.Value)) { $RemoveTypeAccelerator.Invoke($null, @($Entry.Key)) | Out-Null } } } finally { if ($null -ne $PreviousPowerForgeOnRemove) { & $PreviousPowerForgeOnRemove @args } } }.GetNewClosure() } } try { & $RegisterPowerForgeDesktopAssemblyTypeAccelerators -LibraryDirectory ($PowerForgeDesktopBinaryDirectory) } catch { Write-Warning -Message "Desktop type accelerator registration failed: $($_.Exception.Message)" } } } } if ($PSEdition -ne 'Core' -and $null -ne $PowerForgeDesktopAssemblyResolverState) { $PowerForgeDesktopAssemblyResolverState.BootstrapActive = $false if ($null -ne $UnregisterPowerForgeDesktopAssemblyResolver) { & $UnregisterPowerForgeDesktopAssemblyResolver } } # PowerForge script payload begin # PowerForge script payload end $FunctionsToExport = @() $CmdletsToExport = @('Connect-HomeAssistant', 'Disconnect-HomeAssistant', 'Export-HomeAssistantCameraSnapshot', 'Export-HomeAssistantDiagnostic', 'Find-HomeAssistant', 'Get-HomeAssistantAction', 'Get-HomeAssistantApp', 'Get-HomeAssistantArea', 'Get-HomeAssistantAutomation', 'Get-HomeAssistantBackup', 'Get-HomeAssistantCalendar', 'Get-HomeAssistantCalendarEvent', 'Get-HomeAssistantCamera', 'Get-HomeAssistantCategory', 'Get-HomeAssistantConnection', 'Get-HomeAssistantDashboard', 'Get-HomeAssistantDevice', 'Get-HomeAssistantEnergy', 'Get-HomeAssistantEntity', 'Get-HomeAssistantFloor', 'Get-HomeAssistantHistory', 'Get-HomeAssistantInfo', 'Get-HomeAssistantIntegration', 'Get-HomeAssistantIssue', 'Get-HomeAssistantJob', 'Get-HomeAssistantLabel', 'Get-HomeAssistantLog', 'Get-HomeAssistantLogbook', 'Get-HomeAssistantMedia', 'Get-HomeAssistantNotification', 'Get-HomeAssistantStatistic', 'Get-HomeAssistantTrace', 'Get-HomeAssistantUpdate', 'Get-HomeAssistantWeather', 'Install-HomeAssistantUpdate', 'Invoke-HomeAssistantAction', 'Invoke-HomeAssistantApp', 'Invoke-HomeAssistantAutomation', 'Invoke-HomeAssistantRecorderMaintenance', 'Invoke-HomeAssistantRemote', 'Invoke-HomeAssistantRoutine', 'New-HomeAssistantBackup', 'Receive-HomeAssistantCalendarEvent', 'Receive-HomeAssistantEvent', 'Receive-HomeAssistantNotification', 'Receive-HomeAssistantWeatherForecast', 'Remove-HomeAssistantAutomation', 'Remove-HomeAssistantCalendarEvent', 'Remove-HomeAssistantCategory', 'Remove-HomeAssistantDashboard', 'Remove-HomeAssistantLabel', 'Remove-HomeAssistantNotification', 'Remove-HomeAssistantStatistic', 'Restart-HomeAssistant', 'Send-HomeAssistantNotification', 'Set-HomeAssistantAlarm', 'Set-HomeAssistantAutomation', 'Set-HomeAssistantCalendarEvent', 'Set-HomeAssistantCamera', 'Set-HomeAssistantCategory', 'Set-HomeAssistantClimate', 'Set-HomeAssistantCover', 'Set-HomeAssistantDashboard', 'Set-HomeAssistantEnergy', 'Set-HomeAssistantFan', 'Set-HomeAssistantHelper', 'Set-HomeAssistantHumidifier', 'Set-HomeAssistantLabel', 'Set-HomeAssistantLawnMower', 'Set-HomeAssistantLight', 'Set-HomeAssistantLock', 'Set-HomeAssistantMediaPlayer', 'Set-HomeAssistantSiren', 'Set-HomeAssistantStatistic', 'Set-HomeAssistantSwitch', 'Set-HomeAssistantVacuum', 'Set-HomeAssistantValve', 'Set-HomeAssistantWaterHeater', 'Test-HomeAssistantAutomationDraft', 'Test-HomeAssistantConfiguration', 'Test-HomeAssistantStatistic') $AliasesToExport = @() Export-ModuleMember -Function $FunctionsToExport -Alias $AliasesToExport -Cmdlet $CmdletsToExport |