Add comprehensive test suite, fix minor issues, rename function and file names to match approved verbs (#708)

This commit is contained in:
Jeffrey
2026-07-19 22:06:07 +02:00
committed by GitHub
parent a7292e4f35
commit 9c033dbf98
116 changed files with 4629 additions and 650 deletions
@@ -1,5 +1,8 @@
# Add parameter to script and write to file
function AddParameter {
<#
.SYNOPSIS
Adds or updates a value in the active parameter collection.
#>
function Add-Parameter {
param (
$parameterName,
$value = $true
@@ -39,25 +39,6 @@ function Convert-RegOperationToValueKind {
}
}
function Remove-RegistrySubKeyTreeIfExists {
param(
[Parameter(Mandatory)]
[Microsoft.Win32.RegistryKey]$RootKey,
[Parameter(Mandatory)]
[string]$SubKeyPath
)
try {
$RootKey.DeleteSubKeyTree($SubKeyPath, $false)
}
catch [System.UnauthorizedAccessException], [System.Security.SecurityException] {
throw
}
catch {
# Best-effort cleanup only; missing keys are fine.
}
}
function Get-RegistryKeyForOperation {
param(
[Parameter(Mandatory)]
@@ -1,7 +1,8 @@
# Shows confirmation dialogs for apps that require extra caution before removal.
# Returns $true if the user confirmed all warnings (or if no warnings were triggered),
# $false if the user declined any warning.
function ConfirmUnsafeAppRemoval {
<#
.SYNOPSIS
Confirms removal of applications that require an extra safety warning.
#>
function Confirm-UnsafeAppRemoval {
param (
[string[]]$SelectedApps,
$Owner = $null
@@ -1,5 +1,8 @@
# Generates a list of apps to remove based on the Apps parameter
function GenerateAppsList {
<#
.SYNOPSIS
Builds the validated application-removal list from the Apps parameter.
#>
function Generate-AppsList {
if (-not ($script:Params["Apps"] -and $script:Params["Apps"] -is [string])) {
return @()
}
@@ -8,12 +11,12 @@ function GenerateAppsList {
switch ($appMode) {
'default' {
$appsList = LoadAppsFromFile $script:AppsListFilePath
$appsList = Import-AppsFromFile $script:AppsListFilePath
return $appsList
}
default {
$appsList = $script:Params["Apps"].Split(',') | ForEach-Object { $_.Trim() }
$validatedAppsList = ValidateAppslist $appsList
$validatedAppsList = Get-ValidatedAppList $appsList
return $validatedAppsList
}
}
@@ -1,4 +1,8 @@
function GetFriendlyRegistryBackupTarget {
<#
.SYNOPSIS
Converts a registry-backup target identifier into a user-friendly label.
#>
function Get-FriendlyRegistryBackupTarget {
param(
[AllowNull()]
[AllowEmptyString()]
@@ -40,4 +44,4 @@ function GetFriendlyRegistryBackupTarget {
}
return $Target
}
}
@@ -0,0 +1,13 @@
<#
.SYNOPSIS
Returns a readable description of the current app-removal target.
#>
function Get-FriendlyTargetUserName {
$target = Get-TargetUserForAppRemoval
switch ($target) {
"AllUsers" { return "all users" }
"CurrentUser" { return "the current user" }
default { return "user $target" }
}
}
+31 -6
View File
@@ -74,7 +74,10 @@ function Get-RegFileOperations {
}
$parsedValue = Convert-RegValueData -valueData $matches.valueData.Trim()
if (-not $parsedValue) { continue }
if (-not $parsedValue) {
Write-Warning "Skipping unsupported or malformed registry value '$valueName' in '$currentKeyPath'."
continue
}
$operations += [PSCustomObject]@{
OperationType = $parsedValue.OperationType
@@ -88,6 +91,10 @@ function Get-RegFileOperations {
return $operations
}
<#
.SYNOPSIS
Converts a .reg value literal into an operation type, registry value type, and data.
#>
function Convert-RegValueData {
param(
[Parameter(Mandatory)]
@@ -121,8 +128,13 @@ function Convert-RegValueData {
}
if ($valueData -match '^hex(?:\((?<kind>[0-9a-fA-F]+)\))?:(?<bytes>[0-9a-fA-F,\s]+)$') {
$bytes = Convert-HexStringToByteArray -hexValue $matches.bytes
$parsedBytes = Convert-HexStringToByteArray -hexValue $matches.bytes
if ($null -eq $parsedBytes) {
return $null
}
$bytes = [byte[]]@($parsedBytes)
$valueType = if ($matches.kind) { "Hex$($matches.kind)" } else { 'Binary' }
$value = switch ($matches.kind) {
'2' { Convert-RegistryByteArrayToString -byteData $bytes }
'7' { Convert-RegistryByteArrayToMultiString -byteData $bytes }
@@ -150,16 +162,29 @@ function Convert-RegValueData {
return $null
}
<#
.SYNOPSIS
Converts a comma-separated hexadecimal byte string into a byte array.
#>
function Convert-HexStringToByteArray {
param(
[Parameter(Mandatory)]
[string]$hexValue
)
$parts = $hexValue.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ }
return [System.Linq.Enumerable]::Select($parts, [Func[object, byte]] {
param($h) [System.Convert]::ToByte($h, 16)
}) -as [byte[]]
$parts = @($hexValue.Split(',') | ForEach-Object { $_.Trim() })
if ($parts | Where-Object { [string]::IsNullOrWhiteSpace($_) }) {
return $null
}
$bytes = New-Object byte[] $parts.Count
for ($i = 0; $i -lt $parts.Count; $i++) {
if ($parts[$i] -notmatch '^[0-9a-fA-F]{1,2}$') {
return $null
}
$bytes[$i] = [System.Convert]::ToByte($parts[$i], 16)
}
return ,$bytes
}
function Convert-RegistryByteArrayToString {
@@ -1,6 +1,6 @@
# Target is determined from $script:Params["AppRemovalTarget"] or defaults to "AllUsers"
# Target values: "AllUsers" (removes for all users + from image), "CurrentUser", or a specific username
function GetTargetUserForAppRemoval {
function Get-TargetUserForAppRemoval {
if ($script:Params.ContainsKey("AppRemovalTarget")) {
return $script:Params["AppRemovalTarget"]
}
@@ -1,5 +1,5 @@
# Returns the directory path of the specified user, exits script if user path can't be found
function GetUserDirectory {
function Get-UserDirectory {
param (
$userName,
$fileName = "",
@@ -29,7 +29,7 @@ function GetUserDirectory {
}
}
$userContext = ResolveUserProfileContext -UserName $userName
$userContext = Resolve-UserProfileContext -UserName $userName
$resolvedUserDirectory = if ($userContext) { $userContext.ProfilePath } else { $null }
if ($resolvedUserDirectory) {
$userPath = if ([string]::IsNullOrWhiteSpace($fileName)) {
@@ -46,9 +46,9 @@ function GetUserDirectory {
}
catch {
Write-Error "Something went wrong when trying to find the user directory path for user $userName. Please ensure the user exists on this system"
AwaitKeyToExit
Wait-ForKeyPress
}
Write-Error "Unable to find user directory path for user $userName"
AwaitKeyToExit
Wait-ForKeyPress
}
+11
View File
@@ -0,0 +1,11 @@
<#
.SYNOPSIS
Returns the explicitly targeted user name or the current process user name.
#>
function Get-UserName {
if ($script:Params.ContainsKey("User")) {
return $script:Params.Item("User")
}
return $env:USERNAME
}
@@ -1,9 +0,0 @@
function GetFriendlyTargetUserName {
$target = GetTargetUserForAppRemoval
switch ($target) {
"AllUsers" { return "all users" }
"CurrentUser" { return "the current user" }
default { return "user $target" }
}
}
-7
View File
@@ -1,7 +0,0 @@
function GetUserName {
if ($script:Params.ContainsKey("User")) {
return $script:Params.Item("User")
}
return $env:USERNAME
}
@@ -1,4 +1,8 @@
function ImportConfigToParams {
<#
.SYNOPSIS
Imports valid application, tweak, and deployment selections from a configuration JSON file into active parameters.
#>
function Import-ConfigToParams {
param (
[Parameter(Mandatory = $true)]
[string]$ConfigPath,
@@ -22,7 +26,7 @@ function ImportConfigToParams {
throw "Provided config file must be a .json file: $resolvedConfigPath"
}
$configJson = LoadJsonFile -filePath $resolvedConfigPath -expectedVersion $ExpectedVersion
$configJson = Import-JsonFile -filePath $resolvedConfigPath -expectedVersion $ExpectedVersion
if ($null -eq $configJson) {
throw "Failed to read config file: $resolvedConfigPath"
}
@@ -38,8 +42,8 @@ function ImportConfigToParams {
)
if ($appIds.Count -gt 0) {
AddParameter 'RemoveApps'
AddParameter 'Apps' ($appIds -join ',')
Add-Parameter 'RemoveApps'
Add-Parameter 'Apps' ($appIds -join ',')
$importedItems++
}
}
@@ -59,7 +63,7 @@ function ImportConfigToParams {
continue
}
AddParameter $setting.Name $true
Add-Parameter $setting.Name $true
$importedItems++
}
}
@@ -73,12 +77,12 @@ function ImportConfigToParams {
}
if ($deploymentLookup.ContainsKey('CreateRestorePoint') -and [bool]$deploymentLookup['CreateRestorePoint']) {
AddParameter 'CreateRestorePoint'
Add-Parameter 'CreateRestorePoint'
$importedItems++
}
if ($deploymentLookup.ContainsKey('RestartExplorer') -and -not [bool]$deploymentLookup['RestartExplorer']) {
AddParameter 'NoRestartExplorer'
Add-Parameter 'NoRestartExplorer'
$importedItems++
}
@@ -87,12 +91,12 @@ function ImportConfigToParams {
1 {
$otherUserName = if ($deploymentLookup.ContainsKey('OtherUsername')) { "$($deploymentLookup['OtherUsername'])".Trim() } else { '' }
if (-not [string]::IsNullOrWhiteSpace($otherUserName)) {
AddParameter 'User' $otherUserName
Add-Parameter 'User' $otherUserName
$importedItems++
}
}
2 {
AddParameter 'Sysprep'
Add-Parameter 'Sysprep'
$importedItems++
}
}
@@ -101,17 +105,17 @@ function ImportConfigToParams {
if ($deploymentLookup.ContainsKey('AppRemovalScopeIndex') -and $script:Params.ContainsKey('RemoveApps')) {
switch ([int]$deploymentLookup['AppRemovalScopeIndex']) {
0 {
AddParameter 'AppRemovalTarget' 'AllUsers'
Add-Parameter 'AppRemovalTarget' 'AllUsers'
$importedItems++
}
1 {
AddParameter 'AppRemovalTarget' 'CurrentUser'
Add-Parameter 'AppRemovalTarget' 'CurrentUser'
$importedItems++
}
2 {
$targetUser = if ($deploymentLookup.ContainsKey('OtherUsername')) { "$($deploymentLookup['OtherUsername'])".Trim() } else { '' }
if (-not [string]::IsNullOrWhiteSpace($targetUser)) {
AddParameter 'AppRemovalTarget' $targetUser
Add-Parameter 'AppRemovalTarget' $targetUser
$importedItems++
}
}
@@ -1,3 +1,7 @@
<#
.SYNOPSIS
Normalizes a rooted registry path and returns its hive and subkey components.
#>
function Split-RegistryPath {
param(
[Parameter(Mandatory)]
@@ -51,6 +55,10 @@ function Split-RegistryPath {
}
}
<#
.SYNOPSIS
Returns the .NET registry root key for a supported registry hive name.
#>
function Get-RegistryRootKey {
param(
[Parameter(Mandatory)]
@@ -67,6 +75,39 @@ function Get-RegistryRootKey {
}
}
<#
.SYNOPSIS
Deletes a registry subkey tree and ignores a key that has already disappeared.
#>
function Remove-RegistrySubKeyTreeIfExists {
param(
[Parameter(Mandatory)]
$RootKey,
[Parameter(Mandatory)]
[string]$SubKeyPath
)
try {
$RootKey.DeleteSubKeyTree($SubKeyPath, $false)
}
catch {
$failure = $_.Exception
while ($failure.InnerException) {
$failure = $failure.InnerException
}
if ($failure -is [System.ArgumentException]) {
# The key can disappear between snapshot inspection and deletion.
return
}
throw
}
}
<#
.SYNOPSIS
Returns a feature's registry-file path, using the Sysprep layout when targeting another profile.
#>
function Get-RegistryFilePathForFeature {
param(
[Parameter(Mandatory)]
@@ -12,7 +12,7 @@
.OUTPUTS
System.String
#>
function NormalizeUserLookupValue {
function Normalize-UserLookupValue {
param(
[string]$Value
)
@@ -44,12 +44,12 @@ if (-not $script:ResolvedUserSidCache) {
.OUTPUTS
System.String
#>
function GetUserLookupCacheKey {
function Get-UserLookupCacheKey {
param(
[string]$Value
)
$normalizedValue = NormalizeUserLookupValue -Value $Value
$normalizedValue = Normalize-UserLookupValue -Value $Value
if ([string]::IsNullOrWhiteSpace($normalizedValue)) {
return ''
}
@@ -72,13 +72,13 @@ function GetUserLookupCacheKey {
.OUTPUTS
System.String[]
#>
function GetNormalizedLookupCandidates {
function Get-NormalizedLookupCandidates {
param(
[string[]]$Candidates
)
$normalized = @($Candidates) |
ForEach-Object { NormalizeUserLookupValue -Value $_ } |
ForEach-Object { Normalize-UserLookupValue -Value $_ } |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Select-Object -Unique
@@ -100,7 +100,7 @@ function GetNormalizedLookupCandidates {
.OUTPUTS
System.String
#>
function EscapeWqlString {
function Escape-WqlString {
param(
[string]$Value
)
@@ -126,22 +126,22 @@ function EscapeWqlString {
.OUTPUTS
System.String
#>
function GetLocalUserNameSegment {
function Get-LocalUserNameSegment {
param(
[string]$UserName
)
$normalizedName = NormalizeUserLookupValue -Value $UserName
$normalizedName = Normalize-UserLookupValue -Value $UserName
if ([string]::IsNullOrWhiteSpace($normalizedName)) {
return ''
}
if ($normalizedName.Contains('\')) {
return NormalizeUserLookupValue -Value (($normalizedName -split '\\')[-1])
return Normalize-UserLookupValue -Value (($normalizedName -split '\\')[-1])
}
if ($normalizedName.Contains('@')) {
return NormalizeUserLookupValue -Value (($normalizedName -split '@')[0])
return Normalize-UserLookupValue -Value (($normalizedName -split '@')[0])
}
return $normalizedName
@@ -165,12 +165,12 @@ function GetLocalUserNameSegment {
.OUTPUTS
System.String
#>
function ResolveNetBiosDomainName {
function Resolve-NetBiosDomainName {
param(
[string]$RawDomain
)
$trimmed = NormalizeUserLookupValue -Value $RawDomain
$trimmed = Normalize-UserLookupValue -Value $RawDomain
if ([string]::IsNullOrWhiteSpace($trimmed)) {
return ''
}
@@ -194,7 +194,7 @@ function ResolveNetBiosDomainName {
}
if ($ntDomainInstance -and -not [string]::IsNullOrWhiteSpace($ntDomainInstance.DomainName)) {
$fromNtDomain = NormalizeUserLookupValue -Value $ntDomainInstance.DomainName
$fromNtDomain = Normalize-UserLookupValue -Value $ntDomainInstance.DomainName
if (-not [string]::IsNullOrWhiteSpace($fromNtDomain)) {
return $fromNtDomain
}
@@ -205,7 +205,7 @@ function ResolveNetBiosDomainName {
}
if ($trimmed.Contains('.')) {
$leaf = NormalizeUserLookupValue -Value (($trimmed -split '\.')[0])
$leaf = Normalize-UserLookupValue -Value (($trimmed -split '\.')[0])
if (-not [string]::IsNullOrWhiteSpace($leaf)) {
return $leaf
}
@@ -221,7 +221,7 @@ function ResolveNetBiosDomainName {
.DESCRIPTION
Cached in script scope for the process lifetime. Returns $false on
error or workgroup. When joined, also caches the NetBIOS domain label
(ResolveNetBiosDomainName) for use as a profile-folder suffix.
(Resolve-NetBiosDomainName) for use as a profile-folder suffix.
.OUTPUTS
System.Boolean
@@ -239,7 +239,7 @@ function Test-MachineIsDomainJoined {
$computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
if ($null -ne $computerSystem -and $computerSystem.PartOfDomain) {
$script:MachineIsDomainJoined = $true
$script:MachineNetBiosDomain = ResolveNetBiosDomainName -RawDomain ([string]$computerSystem.Domain)
$script:MachineNetBiosDomain = Resolve-NetBiosDomainName -RawDomain ([string]$computerSystem.Domain)
}
}
catch {
@@ -262,7 +262,7 @@ function Test-MachineIsDomainJoined {
.OUTPUTS
System.String
#>
function GetProfileFolderDomainSuffix {
function Get-ProfileFolderDomainSuffix {
if (-not (Test-MachineIsDomainJoined)) {
return ''
}
@@ -301,12 +301,12 @@ function GetProfileFolderDomainSuffix {
.OUTPUTS
System.String[]
#>
function GetUserNameMatchCandidates {
function Get-UserNameMatchCandidates {
param(
[string]$Value
)
$normalized = NormalizeUserLookupValue -Value $Value
$normalized = Normalize-UserLookupValue -Value $Value
if ([string]::IsNullOrWhiteSpace($normalized)) {
return @()
}
@@ -314,13 +314,13 @@ function GetUserNameMatchCandidates {
$candidates = New-Object 'System.Collections.Generic.List[string]'
[void]$candidates.Add($normalized)
$localSegment = GetLocalUserNameSegment -UserName $normalized
$localSegment = Get-LocalUserNameSegment -UserName $normalized
if (-not [string]::IsNullOrWhiteSpace($localSegment) -and ($localSegment -ine $normalized)) {
[void]$candidates.Add($localSegment)
}
# Domain-suffixed forms only apply where Windows writes them.
$domainSuffix = GetProfileFolderDomainSuffix
$domainSuffix = Get-ProfileFolderDomainSuffix
if (-not [string]::IsNullOrWhiteSpace($domainSuffix)) {
# Prefer the local segment as the stem so DOMAIN\user still yields
# user.CONTOSO rather than the fully qualified string.
@@ -340,7 +340,7 @@ function GetUserNameMatchCandidates {
# (registry, backup metadata), so add that form too.
$suffixWithDot = ".$domainSuffix"
if ($normalized.Length -gt $suffixWithDot.Length -and $normalized.EndsWith($suffixWithDot, [System.StringComparison]::OrdinalIgnoreCase)) {
$bareStem = NormalizeUserLookupValue -Value ($normalized.Substring(0, $normalized.Length - $suffixWithDot.Length))
$bareStem = Normalize-UserLookupValue -Value ($normalized.Substring(0, $normalized.Length - $suffixWithDot.Length))
if (-not [string]::IsNullOrWhiteSpace($bareStem)) {
$alreadyPresent = $false
foreach ($existing in $candidates) {
@@ -361,7 +361,7 @@ function GetUserNameMatchCandidates {
Test whether a user name and a profile folder leaf share an account.
.DESCRIPTION
Compares candidate sets (via GetUserNameMatchCandidates) instead of
Compares candidate sets (via Get-UserNameMatchCandidates) instead of
raw strings, so different forms of the same account still match.
.PARAMETER UserName
@@ -383,8 +383,8 @@ function Test-UserNameMatchesProfileLeaf {
return $false
}
$leafCandidates = @(GetUserNameMatchCandidates -Value $ProfileLeaf)
$userCandidates = @(GetUserNameMatchCandidates -Value $UserName)
$leafCandidates = @(Get-UserNameMatchCandidates -Value $ProfileLeaf)
$userCandidates = @(Get-UserNameMatchCandidates -Value $UserName)
foreach ($leaf in $leafCandidates) {
foreach ($user in $userCandidates) {
@@ -430,13 +430,13 @@ function Test-UserNameMatch {
# Workgroup: strict equality (no suffix disambiguation available).
if (-not (Test-MachineIsDomainJoined)) {
$normalizedA = NormalizeUserLookupValue -Value $UserNameA
$normalizedB = NormalizeUserLookupValue -Value $UserNameB
$normalizedA = Normalize-UserLookupValue -Value $UserNameA
$normalizedB = Normalize-UserLookupValue -Value $UserNameB
return ($normalizedA -ieq $normalizedB)
}
$candidatesA = @(GetUserNameMatchCandidates -Value $UserNameA)
$candidatesB = @(GetUserNameMatchCandidates -Value $UserNameB)
$candidatesA = @(Get-UserNameMatchCandidates -Value $UserNameA)
$candidatesB = @(Get-UserNameMatchCandidates -Value $UserNameB)
foreach ($a in $candidatesA) {
foreach ($b in $candidatesB) {
@@ -463,7 +463,7 @@ function Test-UserNameMatch {
.PARAMETER Sid
Resolved SID to cache.
#>
function SetResolvedUserSidCache {
function Set-ResolvedUserSidCache {
param(
[string[]]$Candidates,
[string]$Sid
@@ -474,7 +474,7 @@ function SetResolvedUserSidCache {
}
foreach ($candidate in @($Candidates)) {
$cacheKey = GetUserLookupCacheKey -Value $candidate
$cacheKey = Get-UserLookupCacheKey -Value $candidate
if ($cacheKey) {
$script:ResolvedUserSidCache[$cacheKey] = $Sid
}
@@ -494,13 +494,13 @@ function SetResolvedUserSidCache {
.OUTPUTS
System.String
#>
function GetCachedResolvedUserSid {
function Get-CachedResolvedUserSid {
param(
[string[]]$Candidates
)
foreach ($candidate in @($Candidates)) {
$cacheKey = GetUserLookupCacheKey -Value $candidate
$cacheKey = Get-UserLookupCacheKey -Value $candidate
if ($cacheKey -and $script:ResolvedUserSidCache.ContainsKey($cacheKey)) {
return $script:ResolvedUserSidCache[$cacheKey]
}
@@ -523,7 +523,7 @@ function GetCachedResolvedUserSid {
.OUTPUTS
System.String
#>
function TryResolveSidByNtAccount {
function Try-ResolveSidByNtAccount {
param(
[string]$UserName
)
@@ -560,12 +560,12 @@ function TryResolveSidByNtAccount {
.OUTPUTS
System.String
#>
function TryResolveSidByLocalLookup {
function Try-ResolveSidByLocalLookup {
param(
[string[]]$Candidates
)
$lookupCandidates = GetNormalizedLookupCandidates -Candidates $Candidates
$lookupCandidates = Get-NormalizedLookupCandidates -Candidates $Candidates
if ($lookupCandidates.Count -eq 0) {
return $null
}
@@ -586,8 +586,8 @@ function TryResolveSidByLocalLookup {
foreach ($candidate in $lookupCandidates) {
try {
$escapedCandidate = EscapeWqlString -Value $candidate
$escapedComputerName = EscapeWqlString -Value $env:COMPUTERNAME
$escapedCandidate = Escape-WqlString -Value $candidate
$escapedComputerName = Escape-WqlString -Value $env:COMPUTERNAME
$filter = "LocalAccount=True AND (Name='$escapedCandidate' OR FullName='$escapedCandidate' OR Caption='$escapedComputerName\$escapedCandidate')"
$matchingAccount = Get-CimInstance -ClassName Win32_UserAccount -Filter $filter -ErrorAction Stop | Select-Object -First 1
@@ -617,12 +617,12 @@ function TryResolveSidByLocalLookup {
.OUTPUTS
System.String
#>
function TryResolveSidFromProfileList {
function Try-ResolveSidFromProfileList {
param(
[string[]]$Candidates
)
$lookupCandidates = GetNormalizedLookupCandidates -Candidates $Candidates
$lookupCandidates = Get-NormalizedLookupCandidates -Candidates $Candidates
if ($lookupCandidates.Count -eq 0) {
return $null
}
@@ -635,7 +635,7 @@ function TryResolveSidFromProfileList {
if ([string]::IsNullOrWhiteSpace($imagePath)) { continue }
$expandedPath = [System.Environment]::ExpandEnvironmentVariables($imagePath)
$leafName = NormalizeUserLookupValue -Value (Split-Path -Leaf $expandedPath)
$leafName = Normalize-UserLookupValue -Value (Split-Path -Leaf $expandedPath)
foreach ($candidate in $lookupCandidates) {
if (Test-MachineIsDomainJoined) {
@@ -680,7 +680,7 @@ function TryResolveSidFromProfileList {
.OUTPUTS
System.Management.Automation.PSCustomObject
#>
function NewResolvedUserContext {
function New-ResolvedUserContext {
param(
[string]$UserName,
[string]$UserSid,
@@ -708,12 +708,12 @@ function NewResolvedUserContext {
.OUTPUTS
System.String
#>
function GetQualifiedProcessIdentityName {
function Get-QualifiedProcessIdentityName {
param(
[string]$Candidate
)
$normalizedCandidate = NormalizeUserLookupValue -Value $Candidate
$normalizedCandidate = Normalize-UserLookupValue -Value $Candidate
if ([string]::IsNullOrWhiteSpace($normalizedCandidate)) {
return $null
}
@@ -735,7 +735,7 @@ function GetQualifiedProcessIdentityName {
return $null
}
$currentLocalSegment = GetLocalUserNameSegment -UserName $currentName
$currentLocalSegment = Get-LocalUserNameSegment -UserName $currentName
if (-not [string]::IsNullOrWhiteSpace($currentLocalSegment) -and $currentLocalSegment -ieq $normalizedCandidate) {
return $currentName
}
@@ -762,19 +762,19 @@ function GetQualifiedProcessIdentityName {
.OUTPUTS
System.String
#>
function ResolveUserSid {
function Resolve-UserSid {
param(
[Parameter(Mandatory)]
[string]$UserName
)
$candidateUserName = NormalizeUserLookupValue -Value $UserName
$candidateUserName = Normalize-UserLookupValue -Value $UserName
if ([string]::IsNullOrWhiteSpace($candidateUserName)) {
return $null
}
$hasQualifiedIdentity = $candidateUserName.Contains('\') -or $candidateUserName.Contains('@')
$localNameSegment = GetLocalUserNameSegment -UserName $candidateUserName
$localNameSegment = Get-LocalUserNameSegment -UserName $candidateUserName
$leafNameCandidates = @()
if ($hasQualifiedIdentity -and -not [string]::IsNullOrWhiteSpace($localNameSegment) -and $localNameSegment -ine $candidateUserName) {
$leafNameCandidates = @($localNameSegment)
@@ -796,7 +796,7 @@ function ResolveUserSid {
@($candidateUserName)
}
$cachedSid = GetCachedResolvedUserSid -Candidates $lookupCandidates
$cachedSid = Get-CachedResolvedUserSid -Candidates $lookupCandidates
if ($cachedSid) {
return $cachedSid
}
@@ -811,12 +811,12 @@ function ResolveUserSid {
}
elseif (Test-MachineIsDomainJoined) {
# Prefer process identity (authoritative), then USERDOMAIN\input.
$processQualifiedName = GetQualifiedProcessIdentityName -Candidate $candidateUserName
$processQualifiedName = Get-QualifiedProcessIdentityName -Candidate $candidateUserName
if (-not [string]::IsNullOrWhiteSpace($processQualifiedName)) {
[void]$qualifiedNamesToTry.Add($processQualifiedName)
}
$domainSuffix = GetProfileFolderDomainSuffix
$domainSuffix = Get-ProfileFolderDomainSuffix
if (-not [string]::IsNullOrWhiteSpace($domainSuffix)) {
$domainQualifiedName = "$domainSuffix\$candidateUserName"
if (-not ($qualifiedNamesToTry -contains $domainQualifiedName)) {
@@ -831,10 +831,10 @@ function ResolveUserSid {
# Step 2: resolve qualified form(s) via NTAccount.Translate.
foreach ($qualifiedName in $qualifiedNamesToTry) {
$resolvedSid = TryResolveSidByNtAccount -UserName $qualifiedName
$resolvedSid = Try-ResolveSidByNtAccount -UserName $qualifiedName
if ($resolvedSid) {
$allCacheKeys = @($candidateUserName) + $qualifiedNamesToTry | Select-Object -Unique
SetResolvedUserSidCache -Candidates $allCacheKeys -Sid $resolvedSid
Set-ResolvedUserSidCache -Candidates $allCacheKeys -Sid $resolvedSid
return $resolvedSid
}
}
@@ -842,20 +842,20 @@ function ResolveUserSid {
# Step 3: local SAM fallback (workgroup only; skipped on domain to avoid
# nameshare shadowing).
if (-not (Test-MachineIsDomainJoined)) {
$resolvedSid = TryResolveSidByLocalLookup -Candidates $lookupCandidates
$resolvedSid = Try-ResolveSidByLocalLookup -Candidates $lookupCandidates
if ($resolvedSid) {
$allCacheKeys = @($candidateUserName) + $qualifiedNamesToTry | Select-Object -Unique
SetResolvedUserSidCache -Candidates $allCacheKeys -Sid $resolvedSid
Set-ResolvedUserSidCache -Candidates $allCacheKeys -Sid $resolvedSid
return $resolvedSid
}
}
# Step 4: ProfileList leaf heuristic (last resort; disambiguates by
# on-disk folder name, suffix-aware on domain boxes).
$resolvedSid = TryResolveSidFromProfileList -Candidates $profileHeuristicCandidates
$resolvedSid = Try-ResolveSidFromProfileList -Candidates $profileHeuristicCandidates
if ($resolvedSid) {
$allCacheKeys = @($candidateUserName) + $qualifiedNamesToTry | Select-Object -Unique
SetResolvedUserSidCache -Candidates $allCacheKeys -Sid $resolvedSid
Set-ResolvedUserSidCache -Candidates $allCacheKeys -Sid $resolvedSid
return $resolvedSid
}
@@ -878,13 +878,13 @@ function ResolveUserSid {
.OUTPUTS
System.Management.Automation.PSCustomObject
#>
function ResolveUserProfileContext {
function Resolve-UserProfileContext {
param(
[Parameter(Mandatory)]
[string]$UserName
)
$candidateUserName = NormalizeUserLookupValue -Value $UserName
$candidateUserName = Normalize-UserLookupValue -Value $UserName
if ([string]::IsNullOrWhiteSpace($candidateUserName)) {
return $null
}
@@ -902,14 +902,14 @@ function ResolveUserProfileContext {
$defaultProfilePath = Join-Path $rootPath 'Default'
if (Test-Path -LiteralPath $defaultProfilePath -PathType Container) {
return (NewResolvedUserContext -UserName $candidateUserName -UserSid $null -ProfilePath $defaultProfilePath)
return (New-ResolvedUserContext -UserName $candidateUserName -UserSid $null -ProfilePath $defaultProfilePath)
}
}
return $null
}
$userSid = ResolveUserSid -UserName $candidateUserName
$userSid = Resolve-UserSid -UserName $candidateUserName
if ($userSid) {
$sidRegistryPath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$userSid"
@@ -919,7 +919,7 @@ function ResolveUserProfileContext {
if (-not [string]::IsNullOrWhiteSpace($registryImagePath)) {
$expandedPath = [System.Environment]::ExpandEnvironmentVariables($registryImagePath)
if (Test-Path -LiteralPath $expandedPath -PathType Container) {
return (NewResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $expandedPath)
return (New-ResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $expandedPath)
}
}
}
@@ -932,7 +932,7 @@ function ResolveUserProfileContext {
$matchingProfiles = @(Get-CimInstance -ClassName Win32_UserProfile -Filter "SID='$userSid'" -ErrorAction Stop)
$resolvedProfile = $matchingProfiles | Where-Object { -not [string]::IsNullOrWhiteSpace($_.LocalPath) } | Select-Object -First 1
if ($resolvedProfile -and (Test-Path -LiteralPath $resolvedProfile.LocalPath -PathType Container)) {
return (NewResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $resolvedProfile.LocalPath)
return (New-ResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $resolvedProfile.LocalPath)
}
}
catch {
@@ -948,7 +948,7 @@ function ResolveUserProfileContext {
# Exact leaf match first (common case; avoids an unnecessary scan).
$candidateUserPath = Join-Path $rootPath $candidateUserName
if (Test-Path -LiteralPath $candidateUserPath -PathType Container) {
return (NewResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $candidateUserPath)
return (New-ResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $candidateUserPath)
}
# Only domain-joined boxes write suffixed folders; scanning workgroup
@@ -957,7 +957,7 @@ function ResolveUserProfileContext {
try {
foreach ($child in @(Get-ChildItem -LiteralPath $rootPath -Directory -ErrorAction SilentlyContinue)) {
if (Test-UserNameMatchesProfileLeaf -UserName $candidateUserName -ProfileLeaf $child.Name) {
return (NewResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $child.FullName)
return (New-ResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $child.FullName)
}
}
}
@@ -1,5 +1,5 @@
# Check if this machine supports S0 Modern Standby power state. Returns true if S0 Modern Standby is supported, false otherwise.
function CheckModernStandbySupport {
function Test-ModernStandbySupport {
$count = 0
try {
+1 -1
View File
@@ -23,7 +23,7 @@ function Test-TargetUserName {
}
}
if (-not (CheckIfUserExists -userName $normalizedUserName)) {
if (-not (Test-UserProfileExists -userName $normalizedUserName)) {
return [PSCustomObject]@{
IsValid = $false
UserName = $normalizedUserName
@@ -1,4 +1,4 @@
function CheckIfUserExists {
function Test-UserProfileExists {
param (
[string]$userName
)
@@ -10,7 +10,7 @@ function CheckIfUserExists {
$lookupName = $userName.Trim()
# Validate special characters against the local username segment (user in DOMAIN\user or user@domain).
$localUserName = GetLocalUserNameSegment -UserName $lookupName
$localUserName = Get-LocalUserNameSegment -UserName $lookupName
if ($localUserName.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -ge 0) {
return $false
@@ -22,7 +22,7 @@ function CheckIfUserExists {
}
try {
$userContext = ResolveUserProfileContext -UserName $lookupName
$userContext = Resolve-UserProfileContext -UserName $lookupName
if (-not $userContext -or [string]::IsNullOrWhiteSpace($userContext.ProfilePath)) {
return $false
}
@@ -31,12 +31,12 @@ function Resolve-TargetUserHiveContext {
[string]$TargetUserName
)
$normalizedTargetUserName = NormalizeUserLookupValue -Value $TargetUserName
$normalizedTargetUserName = Normalize-UserLookupValue -Value $TargetUserName
if ([string]::IsNullOrWhiteSpace($normalizedTargetUserName)) {
throw 'Target user name for registry hive resolution is empty.'
}
$userContext = ResolveUserProfileContext -UserName $normalizedTargetUserName
$userContext = Resolve-UserProfileContext -UserName $normalizedTargetUserName
if (-not $userContext -or [string]::IsNullOrWhiteSpace([string]$userContext.ProfilePath)) {
throw "Unable to resolve profile path for target user '$normalizedTargetUserName'."
}