Providers/AD/Private/Test-ADTestSecretStorePrerequisite.ps1
|
function Test-ADTestSecretStorePrerequisite { <# .SYNOPSIS Tests and installs PowerShell SecretManagement/SecretStore prerequisites .DESCRIPTION Checks for required PowerShell modules for SecretManagement and SecretStore. Automatically installs missing modules if not present. .PARAMETER InstallIfMissing Automatically install missing modules. Defaults to $true. .PARAMETER Scope Installation scope for modules. Defaults to 'CurrentUser' .PARAMETER MaxRetries Maximum number of retry attempts for module installation/import when "in use" errors occur. Defaults to 2. .PARAMETER RetryDelaySeconds Delay in seconds between retry attempts. Defaults to 2. .EXAMPLE Test-ADTestSecretStorePrerequisite Checks and installs missing SecretStore modules .EXAMPLE Test-ADTestSecretStorePrerequisite -InstallIfMissing:$false Only checks for modules without installing .EXAMPLE Test-ADTestSecretStorePrerequisite -MaxRetries 3 -RetryDelaySeconds 5 Checks and installs modules with custom retry settings for handling "in use" errors .OUTPUTS PSCustomObject containing prerequisite check results .NOTES Author: Jeffrey Stuhr Version: 1.0.0 Last Updated: 2025-08-05 Required Modules: - Microsoft.PowerShell.SecretManagement - Microsoft.PowerShell.SecretStore #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [bool]$InstallIfMissing = $true, [Parameter()] [ValidateSet('CurrentUser', 'AllUsers')] [string]$Scope = 'CurrentUser', [Parameter()] [ValidateRange(1, 10)] [int]$MaxRetries = 2, [Parameter()] [ValidateRange(1, 30)] [int]$RetryDelaySeconds = 2 ) begin { $correlationId = [System.Guid]::NewGuid() Write-Verbose "Starting Test-ADTestSecretStorePrerequisite - CorrelationId: $correlationId" $requiredModules = @( 'Microsoft.PowerShell.SecretManagement', 'Microsoft.PowerShell.SecretStore' ) $results = @{ CorrelationId = $correlationId RequiredModules = @() AllModulesAvailable = $true ModulesInstalled = @() ModulesImported = @() Errors = @() } } process { try { Write-Verbose "Checking required modules: $($requiredModules -join ', ')" foreach ($moduleName in $requiredModules) { $moduleResult = @{ Name = $moduleName Available = $false Installed = $false Imported = $false Version = $null Error = $null } try { # Check if module is available $availableModule = Get-Module -ListAvailable -Name $moduleName | Sort-Object Version -Descending | Select-Object -First 1 if ($availableModule) { $moduleResult.Available = $true $moduleResult.Version = $availableModule.Version.ToString() Write-Verbose "Module $moduleName is available (Version: $($moduleResult.Version))" } else { Write-Verbose "Module $moduleName is not available" $results.AllModulesAvailable = $false if ($InstallIfMissing) { Write-Verbose "Installing module: $moduleName" try { # First attempt - normal installation Install-Module -Name $moduleName -Force -Scope $Scope -AllowClobber -ErrorAction Stop $results.ModulesInstalled += $moduleName $moduleResult.Installed = $true $moduleResult.Available = $true # Get version after installation $installedModule = Get-Module -ListAvailable -Name $moduleName | Sort-Object Version -Descending | Select-Object -First 1 if ($installedModule) { $moduleResult.Version = $installedModule.Version.ToString() } Write-Verbose "Successfully installed module: $moduleName (Version: $($moduleResult.Version))" } catch { # Check if it's a "module in use" error if ($_.Exception.Message -like "*currently in use*" -or $_.Exception.Message -like "*version*is currently in use*") { Write-Verbose "Module $moduleName is in use, attempting retry with force unload and wait" $retryAttempt = 0 $retrySuccess = $false while ($retryAttempt -lt $MaxRetries -and -not $retrySuccess) { $retryAttempt++ Write-Verbose "Retry attempt $retryAttempt of $MaxRetries for module: $moduleName" try { # Try to remove the module from memory Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue # Wait for module to be released Write-Verbose "Waiting $RetryDelaySeconds seconds for module to be released..." Start-Sleep -Seconds $RetryDelaySeconds # Retry installation Install-Module -Name $moduleName -Force -Scope $Scope -AllowClobber -SkipPublisherCheck -ErrorAction Stop $results.ModulesInstalled += $moduleName $moduleResult.Installed = $true $moduleResult.Available = $true $retrySuccess = $true # Get version after installation $installedModule = Get-Module -ListAvailable -Name $moduleName | Sort-Object Version -Descending | Select-Object -First 1 if ($installedModule) { $moduleResult.Version = $installedModule.Version.ToString() } Write-Verbose "Successfully installed module on retry $retryAttempt : $moduleName (Version: $($moduleResult.Version))" } catch { Write-Verbose "Retry $retryAttempt failed for $moduleName : $($_.Exception.Message)" if ($retryAttempt -eq $MaxRetries) { # Final attempt failed, check if module is actually available now $retryCheck = Get-Module -ListAvailable -Name $moduleName | Sort-Object Version -Descending | Select-Object -First 1 if ($retryCheck) { Write-Verbose "Module $moduleName appears to be available despite installation error (Version: $($retryCheck.Version))" $moduleResult.Available = $true $moduleResult.Version = $retryCheck.Version.ToString() $moduleResult.Error = "Installation completed with warnings after $retryAttempt retries: $($_.Exception.Message)" $results.Errors += "Installation warning for '$moduleName': $($_.Exception.Message)" $retrySuccess = $true } else { $errorMsg = "Failed to install module '$moduleName' after $MaxRetries retries: $($_.Exception.Message)" $moduleResult.Error = $errorMsg $results.Errors += $errorMsg Write-Error $errorMsg } } } } } else { $errorMsg = "Failed to install module '$moduleName': $($_.Exception.Message)" $moduleResult.Error = $errorMsg $results.Errors += $errorMsg Write-Error $errorMsg continue } } } } # Try to import the module if it's available if ($moduleResult.Available) { try { Import-Module -Name $moduleName -Force -ErrorAction Stop $moduleResult.Imported = $true $results.ModulesImported += $moduleName Write-Verbose "Successfully imported module: $moduleName" } catch { # Check if it's a conflict issue and try alternative approach if ($_.Exception.Message -like "*could not be loaded*" -or $_.Exception.Message -like "*in use*") { Write-Verbose "Module import conflict detected for $moduleName, attempting alternative import" $importRetryAttempt = 0 $importSuccess = $false while ($importRetryAttempt -lt $MaxRetries -and -not $importSuccess) { $importRetryAttempt++ Write-Verbose "Import retry attempt $importRetryAttempt of $MaxRetries for module: $moduleName" try { # Try removing and re-importing Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds $RetryDelaySeconds Import-Module -Name $moduleName -Force -Global -ErrorAction Stop $moduleResult.Imported = $true $results.ModulesImported += $moduleName $importSuccess = $true Write-Verbose "Successfully imported module on retry $importRetryAttempt : $moduleName" } catch { Write-Verbose "Import retry $importRetryAttempt failed for $moduleName : $($_.Exception.Message)" if ($importRetryAttempt -eq $MaxRetries) { # Try to check if module functions are actually available if (Get-Command -Module $moduleName -ErrorAction SilentlyContinue) { Write-Verbose "Module $moduleName functions are available despite import error" $moduleResult.Imported = $true $results.ModulesImported += $moduleName $moduleResult.Error = "Import completed with warnings after $importRetryAttempt retries: $($_.Exception.Message)" $importSuccess = $true } else { $errorMsg = "Failed to import module '$moduleName' after $MaxRetries retries: $($_.Exception.Message)" $moduleResult.Error = $errorMsg $results.Errors += $errorMsg Write-Warning $errorMsg } } } } } else { $errorMsg = "Failed to import module '$moduleName': $($_.Exception.Message)" $moduleResult.Error = $errorMsg $results.Errors += $errorMsg Write-Warning $errorMsg } } } } catch { $errorMsg = "Error processing module '$moduleName': $($_.Exception.Message)" $moduleResult.Error = $errorMsg $results.Errors += $errorMsg Write-Error $errorMsg } $results.RequiredModules += [PSCustomObject]$moduleResult } # Final validation $availableCount = ($results.RequiredModules | Where-Object { $_.Available }).Count $importedCount = ($results.RequiredModules | Where-Object { $_.Imported }).Count Write-Verbose "Prerequisites check complete - Available: $availableCount/$($requiredModules.Count), Imported: $importedCount/$($requiredModules.Count)" if ($availableCount -eq $requiredModules.Count -and $importedCount -eq $requiredModules.Count) { Write-Verbose "All SecretStore prerequisites are satisfied" } else { Write-Warning "Some SecretStore prerequisites are not satisfied" $results.AllModulesAvailable = $false } } catch { $errorMsg = "Failed to check SecretStore prerequisites: $($_.Exception.Message)" $results.Errors += $errorMsg Write-Error $errorMsg -ErrorAction Stop } } end { Write-Verbose "Completed Test-ADTestSecretStorePrerequisite - CorrelationId: $correlationId" return [PSCustomObject]$results } } |