From 78edce94774f13c2a3e17bc21d67f32af34ca17a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:33:49 +0000 Subject: [PATCH 1/8] Initial plan From e453d2b447870d6a2a8c09a0b9718490c456afd8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:39:50 +0000 Subject: [PATCH 2/8] Implement core Context integration functions Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> --- .../Remove-PowerShellGalleryContext.ps1 | 44 +++++++ .../Context/Set-PowerShellGalleryContext.ps1 | 70 ++++++++++ .../Config/Get-PowerShellGalleryConfig.ps1 | 33 +++++ .../Initialize-PowerShellGalleryConfig.ps1 | 60 +++++++++ .../public/Auth/Connect-PowerShellGallery.ps1 | 122 ++++++++++++++++++ .../Context/Get-PowerShellGalleryContext.ps1 | 76 +++++++++++ .../Switch-PowerShellGalleryContext.ps1 | 46 +++++++ .../Auth/Disconnect-PowerShellGallery.ps1 | 58 +++++++++ .../Auth/Get-PowerShellGalleryAccessToken.ps1 | 81 ++++++++++++ .../Auth/Test-PowerShellGalleryAccess.ps1 | 103 +++++++++++++++ src/variables/private/PowerShellGallery.ps1 | 10 ++ 11 files changed, 703 insertions(+) create mode 100644 src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 create mode 100644 src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 create mode 100644 src/functions/private/Config/Get-PowerShellGalleryConfig.ps1 create mode 100644 src/functions/private/Config/Initialize-PowerShellGalleryConfig.ps1 create mode 100644 src/functions/public/Auth/Connect-PowerShellGallery.ps1 create mode 100644 src/functions/public/Auth/Context/Get-PowerShellGalleryContext.ps1 create mode 100644 src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 create mode 100644 src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 create mode 100644 src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 create mode 100644 src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 create mode 100644 src/variables/private/PowerShellGallery.ps1 diff --git a/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 b/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 new file mode 100644 index 0000000..b443d4c --- /dev/null +++ b/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 @@ -0,0 +1,44 @@ +function Remove-PowerShellGalleryContext { + <# + .SYNOPSIS + Remove a PowerShell Gallery context. + + .DESCRIPTION + Remove a PowerShell Gallery context from the vault. + + .EXAMPLE + Remove-PowerShellGalleryContext -ID 'MyAccount' + + Removes the PowerShell Gallery context with ID 'MyAccount'. + #> + [OutputType([void])] + [CmdletBinding(SupportsShouldProcess)] + param( + # The ID of the context to remove. + [Parameter(Mandatory)] + [string] $ID + ) + + begin { + Write-Debug '[Remove-PowerShellGalleryContext] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + if ($PSCmdlet.ShouldProcess("Context [$ID]", 'Remove')) { + Write-Verbose "Removing context: [$ID]" + Remove-Context -ID $ID -Vault $script:PowerShellGallery.ContextVault + + # If this was the default context, clear the default + if ($script:PowerShellGallery.Config.DefaultContext -eq $ID) { + Write-Verbose 'Clearing default context' + $script:PowerShellGallery.Config.DefaultContext = $null + $null = Set-Context -ID $script:PowerShellGallery.DefaultConfig.ID -Context $script:PowerShellGallery.Config -Vault $script:PowerShellGallery.ContextVault + } + } + } + + end { + Write-Debug '[Remove-PowerShellGalleryContext] - End' + } +} diff --git a/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 b/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 new file mode 100644 index 0000000..8c8cac3 --- /dev/null +++ b/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 @@ -0,0 +1,70 @@ +function Set-PowerShellGalleryContext { + <# + .SYNOPSIS + Sets the PowerShell Gallery context and stores it in the context vault. + + .DESCRIPTION + This function sets the PowerShell Gallery context and stores it in the context vault. + The context is used to authenticate with the PowerShell Gallery API. + + .EXAMPLE + $context = @{ + ID = 'MyAccount' + Name = 'MyAccount' + ApiKey = $secureApiKey + GalleryUrl = 'https://www.powershellgallery.com' + ConnectedAt = Get-Date + } + Set-PowerShellGalleryContext -Context $context + + Sets the PowerShell Gallery context with the specified settings as a hashtable. + #> + [OutputType([System.Object])] + [CmdletBinding(SupportsShouldProcess)] + param( + # The ID of the context. + [Parameter(Mandatory)] + [string] $ID, + + # The PowerShell Gallery context to save in the vault. + [Parameter(Mandatory)] + [hashtable] $Context, + + # Set as the default context. + [Parameter()] + [switch] $Default, + + # Pass the context through the pipeline. + [Parameter()] + [switch] $PassThru + ) + + begin { + Write-Debug '[Set-PowerShellGalleryContext] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + Write-Debug "Setting context: [$ID]" + $contextObj = @{} + $Context + $contextObj['ID'] = $ID + + if ($PSCmdlet.ShouldProcess("Context [$ID]", 'Set')) { + $result = Set-Context -ID $ID -Context $contextObj -Vault $script:PowerShellGallery.ContextVault -PassThru + + if ($Default) { + Write-Debug "Setting [$ID] as default context" + $script:PowerShellGallery.Config.DefaultContext = $ID + $null = Set-Context -ID $script:PowerShellGallery.DefaultConfig.ID -Context $script:PowerShellGallery.Config -Vault $script:PowerShellGallery.ContextVault + } + + if ($PassThru) { + return $result + } + } + } + + end { + Write-Debug '[Set-PowerShellGalleryContext] - End' + } +} diff --git a/src/functions/private/Config/Get-PowerShellGalleryConfig.ps1 b/src/functions/private/Config/Get-PowerShellGalleryConfig.ps1 new file mode 100644 index 0000000..dbe96c5 --- /dev/null +++ b/src/functions/private/Config/Get-PowerShellGalleryConfig.ps1 @@ -0,0 +1,33 @@ +function Get-PowerShellGalleryConfig { + <# + .SYNOPSIS + Get the PowerShell Gallery configuration. + + .DESCRIPTION + Get the PowerShell Gallery configuration. Initializes if not already loaded. + + .EXAMPLE + Get-PowerShellGalleryConfig + + Gets the PowerShell Gallery configuration. + #> + [OutputType([System.Object])] + [CmdletBinding()] + param() + + begin { + Write-Debug '[Get-PowerShellGalleryConfig] - Start' + } + + process { + if ($null -eq $script:PowerShellGallery.Config) { + Write-Debug 'Config not initialized, initializing...' + Initialize-PowerShellGalleryConfig + } + return $script:PowerShellGallery.Config + } + + end { + Write-Debug '[Get-PowerShellGalleryConfig] - End' + } +} diff --git a/src/functions/private/Config/Initialize-PowerShellGalleryConfig.ps1 b/src/functions/private/Config/Initialize-PowerShellGalleryConfig.ps1 new file mode 100644 index 0000000..0b1a649 --- /dev/null +++ b/src/functions/private/Config/Initialize-PowerShellGalleryConfig.ps1 @@ -0,0 +1,60 @@ +function Initialize-PowerShellGalleryConfig { + <# + .SYNOPSIS + Initialize the PowerShellGallery module configuration. + + .DESCRIPTION + Initialize the PowerShellGallery module configuration. + + .EXAMPLE + Initialize-PowerShellGalleryConfig + + Initializes the PowerShellGallery module configuration. + + .EXAMPLE + Initialize-PowerShellGalleryConfig -Force + + Forces the initialization of the PowerShellGallery module configuration. + #> + [OutputType([void])] + [CmdletBinding()] + param ( + # Force the initialization of the PowerShellGallery config. + [switch] $Force + ) + + begin { + Write-Debug '[Initialize-PowerShellGalleryConfig] - Start' + } + + process { + Write-Debug "Force: [$Force]" + if ($Force) { + Write-Debug 'Forcing initialization of PowerShellGalleryConfig.' + $config = Set-Context -Context $script:PowerShellGallery.DefaultConfig -Vault $script:PowerShellGallery.ContextVault -PassThru + $script:PowerShellGallery.Config = $config + return + } + + if ($null -ne $script:PowerShellGallery.Config) { + Write-Debug 'PowerShellGalleryConfig already initialized and available in memory.' + return + } + + Write-Debug 'Attempt to load the stored PowerShellGalleryConfig from ContextVault' + $config = Get-Context -ID $script:PowerShellGallery.DefaultConfig.ID -Vault $script:PowerShellGallery.ContextVault + if ($config) { + Write-Debug 'PowerShellGalleryConfig loaded into memory.' + $script:PowerShellGallery.Config = $config + return + } + + Write-Debug 'Initializing PowerShellGalleryConfig from defaults' + $config = Set-Context -Context $script:PowerShellGallery.DefaultConfig -Vault $script:PowerShellGallery.ContextVault -PassThru + $script:PowerShellGallery.Config = $config + } + + end { + Write-Debug '[Initialize-PowerShellGalleryConfig] - End' + } +} diff --git a/src/functions/public/Auth/Connect-PowerShellGallery.ps1 b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 new file mode 100644 index 0000000..6a2e943 --- /dev/null +++ b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 @@ -0,0 +1,122 @@ +function Connect-PowerShellGallery { + <# + .SYNOPSIS + Connects to the PowerShell Gallery by storing an API key context. + + .DESCRIPTION + Connects to the PowerShell Gallery by storing an API key in a secure context vault. + When called without an API key, it opens the user's browser to the PowerShell Gallery + API key management page and prompts for the key. + + .EXAMPLE + Connect-PowerShellGallery -Name 'MyAccount' + + Opens browser to API key page and prompts for API key, then stores it with name 'MyAccount'. + + .EXAMPLE + Connect-PowerShellGallery -Name 'CIAccount' -ApiKey $secureApiKey -Silent + + Stores the provided API key without prompting or informational output. + + .EXAMPLE + $context = Connect-PowerShellGallery -Name 'MyAccount' -PassThru + + Stores the API key and returns the context object. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'The API key is received as clear text from user input.' + )] + [CmdletBinding(DefaultParameterSetName = 'Interactive')] + [OutputType([System.Object])] + param( + # A friendly name/identifier for this connection context + [Parameter(Mandatory)] + [string] $Name, + + # The API key as a SecureString. If not provided, user will be prompted. + [Parameter(ParameterSetName = 'Interactive')] + [Parameter(Mandatory, ParameterSetName = 'NonInteractive')] + [SecureString] $ApiKey, + + # Return the context object after creation + [Parameter()] + [switch] $PassThru, + + # Suppress informational output + [Parameter()] + [switch] $Silent + ) + + begin { + Write-Debug '[Connect-PowerShellGallery] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Open browser to API key page if interactive + if ($PSCmdlet.ParameterSetName -eq 'Interactive' -and -not $ApiKey) { + if (-not $Silent) { + Write-Host '🌐 Opening PowerShell Gallery API key management page...' -ForegroundColor Cyan + } + + $apiKeyUrl = 'https://www.powershellgallery.com/account/apikeys' + + # Try to open browser + try { + if ($IsWindows -or $PSVersionTable.PSVersion.Major -le 5) { + Start-Process $apiKeyUrl + } elseif ($IsMacOS) { + Start-Process 'open' -ArgumentList $apiKeyUrl + } elseif ($IsLinux) { + Start-Process 'xdg-open' -ArgumentList $apiKeyUrl + } + } catch { + Write-Warning "Unable to open browser automatically. Please visit: $apiKeyUrl" + } + + if (-not $Silent) { + Write-Host '' + Write-Host 'Please create or copy your API key from the PowerShell Gallery.' -ForegroundColor Yellow + Write-Host '' + } + } + + # Prompt for API key if not provided + if (-not $ApiKey) { + $ApiKey = Read-Host -Prompt 'Enter your PowerShell Gallery API key' -AsSecureString + } + + # Validate API key is not empty + if ($null -eq $ApiKey) { + Write-Error 'API key is required to connect to PowerShell Gallery.' + return + } + + # Create context object + $context = @{ + ID = $Name + Name = $Name + ApiKey = $ApiKey + GalleryUrl = 'https://www.powershellgallery.com' + ApiUrl = 'https://www.powershellgallery.com/api/v2' + ConnectedAt = Get-Date + } + + # Store context + Write-Verbose "Storing context with ID: [$Name]" + $result = Set-PowerShellGalleryContext -ID $Name -Context $context -Default -PassThru + + if (-not $Silent) { + Write-Host "✓ Successfully connected to PowerShell Gallery as [$Name]" -ForegroundColor Green + } + + if ($PassThru) { + return $result + } + } + + end { + Write-Debug '[Connect-PowerShellGallery] - End' + } +} diff --git a/src/functions/public/Auth/Context/Get-PowerShellGalleryContext.ps1 b/src/functions/public/Auth/Context/Get-PowerShellGalleryContext.ps1 new file mode 100644 index 0000000..5a9073c --- /dev/null +++ b/src/functions/public/Auth/Context/Get-PowerShellGalleryContext.ps1 @@ -0,0 +1,76 @@ +function Get-PowerShellGalleryContext { + <# + .SYNOPSIS + Get the current PowerShell Gallery context. + + .DESCRIPTION + Get the current PowerShell Gallery context. Returns stored contexts from the vault. + + .EXAMPLE + Get-PowerShellGalleryContext + + Gets the default PowerShell Gallery context. + + .EXAMPLE + Get-PowerShellGalleryContext -ID 'MyAccount' + + Gets the PowerShell Gallery context with ID 'MyAccount'. + + .EXAMPLE + Get-PowerShellGalleryContext -ListAvailable + + Lists all available PowerShell Gallery contexts. + #> + [OutputType([System.Object])] + [CmdletBinding(DefaultParameterSetName = 'Get default context')] + param( + # The ID of the context. + [Parameter( + Mandatory, + ParameterSetName = 'Get a named context', + Position = 0 + )] + [Alias('Name')] + [Alias('Context')] + [string] $ID, + + # List all available contexts. + [Parameter( + Mandatory, + ParameterSetName = 'List all available contexts' + )] + [switch] $ListAvailable + ) + + begin { + Write-Debug '[Get-PowerShellGalleryContext] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + switch ($PSCmdlet.ParameterSetName) { + 'Get a named context' { + Write-Debug "Get a named context: [$ID]" + } + 'List all available contexts' { + Write-Debug "ListAvailable: [$ListAvailable]" + $ID = '*' + } + default { + Write-Debug 'Getting default context.' + $ID = $script:PowerShellGallery.Config.DefaultContext + if ([string]::IsNullOrEmpty($ID)) { + Write-Warning "No default PowerShell Gallery context found. Please run 'Connect-PowerShellGallery' to configure a context." + return + } + } + } + + Write-Verbose "Getting the context: [$ID]" + Get-Context -ID $ID -Vault $script:PowerShellGallery.ContextVault | Where-Object { $_.ID -ne $script:PowerShellGallery.DefaultConfig.ID } + } + + end { + Write-Debug '[Get-PowerShellGalleryContext] - End' + } +} diff --git a/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 b/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 new file mode 100644 index 0000000..a7fa5fa --- /dev/null +++ b/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 @@ -0,0 +1,46 @@ +function Switch-PowerShellGalleryContext { + <# + .SYNOPSIS + Switch the default PowerShell Gallery context. + + .DESCRIPTION + Switch the default PowerShell Gallery context to a different stored context. + + .EXAMPLE + Switch-PowerShellGalleryContext -ID 'MyAccount' + + Switches the default PowerShell Gallery context to 'MyAccount'. + #> + [OutputType([void])] + [CmdletBinding(SupportsShouldProcess)] + param( + # The ID of the context to set as default. + [Parameter(Mandatory)] + [string] $ID + ) + + begin { + Write-Debug '[Switch-PowerShellGalleryContext] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Verify the context exists + $context = Get-Context -ID $ID -Vault $script:PowerShellGallery.ContextVault + if (-not $context) { + Write-Error "Context [$ID] not found. Use 'Get-PowerShellGalleryContext -ListAvailable' to see available contexts." + return + } + + if ($PSCmdlet.ShouldProcess("Default context to [$ID]", 'Switch')) { + Write-Verbose "Switching default context to [$ID]" + $script:PowerShellGallery.Config.DefaultContext = $ID + $null = Set-Context -ID $script:PowerShellGallery.DefaultConfig.ID -Context $script:PowerShellGallery.Config -Vault $script:PowerShellGallery.ContextVault + Write-Host "✓ Switched to context [$ID]" -ForegroundColor Green + } + } + + end { + Write-Debug '[Switch-PowerShellGalleryContext] - End' + } +} diff --git a/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 b/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 new file mode 100644 index 0000000..52fd987 --- /dev/null +++ b/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 @@ -0,0 +1,58 @@ +function Disconnect-PowerShellGallery { + <# + .SYNOPSIS + Disconnect from the PowerShell Gallery by removing a stored context. + + .DESCRIPTION + Removes a stored PowerShell Gallery context from the vault. + + .EXAMPLE + Disconnect-PowerShellGallery -Name 'MyAccount' + + Removes the context with name 'MyAccount'. + + .EXAMPLE + Disconnect-PowerShellGallery + + Removes the default context. + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + [OutputType([void])] + param( + # The name of the context to remove. If not specified, removes the default context. + [Parameter()] + [string] $Name + ) + + begin { + Write-Debug '[Disconnect-PowerShellGallery] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Determine which context to remove + if ([string]::IsNullOrEmpty($Name)) { + $Name = $script:PowerShellGallery.Config.DefaultContext + if ([string]::IsNullOrEmpty($Name)) { + Write-Warning 'No default context found. Use -Name to specify a context to remove.' + return + } + } + + # Verify context exists + $context = Get-Context -ID $Name -Vault $script:PowerShellGallery.ContextVault + if (-not $context) { + Write-Error "Context [$Name] not found." + return + } + + if ($PSCmdlet.ShouldProcess("Context [$Name]", 'Disconnect')) { + Remove-PowerShellGalleryContext -ID $Name + Write-Host "✓ Disconnected from PowerShell Gallery context [$Name]" -ForegroundColor Green + } + } + + end { + Write-Debug '[Disconnect-PowerShellGallery] - End' + } +} diff --git a/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 b/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 new file mode 100644 index 0000000..4bf9751 --- /dev/null +++ b/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 @@ -0,0 +1,81 @@ +function Get-PowerShellGalleryAccessToken { + <# + .SYNOPSIS + Retrieve the API key from a stored context. + + .DESCRIPTION + Retrieves the PowerShell Gallery API key from the specified context. + Returns as SecureString by default, or as plain text with -AsPlainText. + + .EXAMPLE + Get-PowerShellGalleryAccessToken + + Gets the API key from the default context as a SecureString. + + .EXAMPLE + Get-PowerShellGalleryAccessToken -Context 'MyAccount' -AsPlainText + + Gets the API key from 'MyAccount' context as plain text. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'Converting from stored SecureString.' + )] + [CmdletBinding()] + [OutputType([System.Object])] + param( + # The context name to retrieve the API key from. If not specified, uses the default context. + [Parameter()] + [string] $Context, + + # Return the API key as plain text instead of SecureString + [Parameter()] + [switch] $AsPlainText + ) + + begin { + Write-Debug '[Get-PowerShellGalleryAccessToken] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Get the context + if ([string]::IsNullOrEmpty($Context)) { + $contextObj = Get-PowerShellGalleryContext + } else { + $contextObj = Get-PowerShellGalleryContext -ID $Context + } + + if (-not $contextObj) { + Write-Error 'No context found. Use Connect-PowerShellGallery to create a context.' + return + } + + # Get the API key + $apiKey = $contextObj.ApiKey + if (-not $apiKey) { + Write-Error "No API key found in context [$($contextObj.Name)]" + return + } + + # Return as plain text if requested + if ($AsPlainText) { + if ($apiKey -is [SecureString]) { + return ConvertFrom-SecureString -SecureString $apiKey -AsPlainText + } else { + return $apiKey + } + } + + # Ensure it's a SecureString + if ($apiKey -is [SecureString]) { + return $apiKey + } else { + return ConvertTo-SecureString -String $apiKey -AsPlainText -Force + } + } + + end { + Write-Debug '[Get-PowerShellGalleryAccessToken] - End' + } +} diff --git a/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 new file mode 100644 index 0000000..8bd32a7 --- /dev/null +++ b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 @@ -0,0 +1,103 @@ +function Test-PowerShellGalleryAccess { + <# + .SYNOPSIS + Validates the stored API key by testing access to the PowerShell Gallery API. + + .DESCRIPTION + Tests access to the PowerShell Gallery API using the stored context. + Attempts to query the API and returns information about the validation. + + .EXAMPLE + Test-PowerShellGalleryAccess + + Tests access using the default context. + + .EXAMPLE + Test-PowerShellGalleryAccess -Context 'MyAccount' + + Tests access using the 'MyAccount' context. + #> + [CmdletBinding()] + [OutputType([System.Object])] + param( + # The context to use. If not specified, uses the default context. + [Parameter()] + [string] $Context + ) + + begin { + Write-Debug '[Test-PowerShellGalleryAccess] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Get the context + if ([string]::IsNullOrEmpty($Context)) { + $contextObj = Get-PowerShellGalleryContext + } else { + $contextObj = Get-PowerShellGalleryContext -ID $Context + } + + if (-not $contextObj) { + Write-Error 'No context found. Use Connect-PowerShellGallery to create a context.' + return + } + + Write-Verbose "Testing access for context: [$($contextObj.Name)]" + + # Get the API key as plain text for the request + $apiKey = Get-PowerShellGalleryAccessToken -Context $contextObj.ID -AsPlainText + + if (-not $apiKey) { + Write-Error 'Failed to retrieve API key from context.' + return + } + + # Test by making a request to the PowerShell Gallery API + # The API key validation can be done by attempting to access the API + try { + Write-Verbose 'Testing API connectivity...' + + # Try to access the API root to validate connectivity + $apiUrl = $contextObj.ApiUrl + $headers = @{ + 'X-NuGet-ApiKey' = $apiKey + } + + # Test basic API access + $response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers -ErrorAction Stop + + $result = [PSCustomObject]@{ + Success = $true + Context = $contextObj.Name + ApiUrl = $apiUrl + TestedAt = Get-Date + Message = 'API access validated successfully' + ConnectedAt = $contextObj.ConnectedAt + } + + Write-Host "✓ API access validated for context [$($contextObj.Name)]" -ForegroundColor Green + return $result + + } catch { + $errorMessage = $_.Exception.Message + Write-Warning "Failed to validate API access: $errorMessage" + + $result = [PSCustomObject]@{ + Success = $false + Context = $contextObj.Name + ApiUrl = $contextObj.ApiUrl + TestedAt = Get-Date + Message = "API access validation failed: $errorMessage" + Error = $_ + ConnectedAt = $contextObj.ConnectedAt + } + + return $result + } + } + + end { + Write-Debug '[Test-PowerShellGalleryAccess] - End' + } +} diff --git a/src/variables/private/PowerShellGallery.ps1 b/src/variables/private/PowerShellGallery.ps1 new file mode 100644 index 0000000..61a41ac --- /dev/null +++ b/src/variables/private/PowerShellGallery.ps1 @@ -0,0 +1,10 @@ +$script:PowerShellGallery = @{ + ContextVault = 'PSModule.PowerShellGallery' + DefaultConfig = @{ + ID = 'Module' + DefaultContext = $null + GalleryUrl = 'https://www.powershellgallery.com' + ApiUrl = 'https://www.powershellgallery.com/api/v2' + } + Config = $null +} From 4b30595d7a0c9b35e7124550e8354461dd6f4c18 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:41:13 +0000 Subject: [PATCH 3/8] Add comprehensive tests for Context integration Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> --- tests/PowerShellGallery.Tests.ps1 | 205 ++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) diff --git a/tests/PowerShellGallery.Tests.ps1 b/tests/PowerShellGallery.Tests.ps1 index 0d9b261..99f81e1 100644 --- a/tests/PowerShellGallery.Tests.ps1 +++ b/tests/PowerShellGallery.Tests.ps1 @@ -14,4 +14,209 @@ { Show-PowerShellGalleryItem } | Should -Not -Throw } } + + Context 'Context Integration - Connect-PowerShellGallery' { + BeforeAll { + # Clean up any existing test contexts + $testContextName = 'TestContext_' + (Get-Random) + $script:TestContextName = $testContextName + } + + AfterAll { + # Clean up test context + try { + $null = Disconnect-PowerShellGallery -Name $script:TestContextName -Confirm:$false -ErrorAction SilentlyContinue + } catch { + # Ignore cleanup errors + } + } + + It 'Should accept Name parameter' { + $secureApiKey = ConvertTo-SecureString -String 'test-api-key-123' -AsPlainText -Force + { Connect-PowerShellGallery -Name $script:TestContextName -ApiKey $secureApiKey -Silent } | Should -Not -Throw + } + + It 'Should create a context' { + $context = Get-PowerShellGalleryContext -ID $script:TestContextName + $context | Should -Not -BeNullOrEmpty + $context.Name | Should -Be $script:TestContextName + } + + It 'Should have PassThru parameter' { + $secureApiKey = ConvertTo-SecureString -String 'test-api-key-456' -AsPlainText -Force + $context = Connect-PowerShellGallery -Name "$($script:TestContextName)_2" -ApiKey $secureApiKey -Silent -PassThru + $context | Should -Not -BeNullOrEmpty + $context.Name | Should -Be "$($script:TestContextName)_2" + # Cleanup + $null = Disconnect-PowerShellGallery -Name "$($script:TestContextName)_2" -Confirm:$false -ErrorAction SilentlyContinue + } + } + + Context 'Context Integration - Get-PowerShellGalleryContext' { + BeforeAll { + # Setup test context + $testContextName = 'GetTestContext_' + (Get-Random) + $script:GetTestContextName = $testContextName + $secureApiKey = ConvertTo-SecureString -String 'test-get-api-key' -AsPlainText -Force + Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent + } + + AfterAll { + # Cleanup + try { + $null = Disconnect-PowerShellGallery -Name $script:GetTestContextName -Confirm:$false -ErrorAction SilentlyContinue + } catch { + # Ignore cleanup errors + } + } + + It 'Should retrieve context by ID' { + $context = Get-PowerShellGalleryContext -ID $script:GetTestContextName + $context | Should -Not -BeNullOrEmpty + $context.Name | Should -Be $script:GetTestContextName + } + + It 'Should list available contexts with -ListAvailable' { + $contexts = Get-PowerShellGalleryContext -ListAvailable + $contexts | Should -Not -BeNullOrEmpty + $contexts.Name | Should -Contain $script:GetTestContextName + } + + It 'Should return default context when no parameters provided' { + # This will either return a context or warn about no default + { Get-PowerShellGalleryContext } | Should -Not -Throw + } + } + + Context 'Context Integration - Switch-PowerShellGalleryContext' { + BeforeAll { + # Setup test contexts + $testContextName1 = 'SwitchTestContext1_' + (Get-Random) + $testContextName2 = 'SwitchTestContext2_' + (Get-Random) + $script:SwitchTestContextName1 = $testContextName1 + $script:SwitchTestContextName2 = $testContextName2 + + $secureApiKey1 = ConvertTo-SecureString -String 'test-switch-api-key-1' -AsPlainText -Force + $secureApiKey2 = ConvertTo-SecureString -String 'test-switch-api-key-2' -AsPlainText -Force + + Connect-PowerShellGallery -Name $testContextName1 -ApiKey $secureApiKey1 -Silent + Connect-PowerShellGallery -Name $testContextName2 -ApiKey $secureApiKey2 -Silent + } + + AfterAll { + # Cleanup + try { + $null = Disconnect-PowerShellGallery -Name $script:SwitchTestContextName1 -Confirm:$false -ErrorAction SilentlyContinue + $null = Disconnect-PowerShellGallery -Name $script:SwitchTestContextName2 -Confirm:$false -ErrorAction SilentlyContinue + } catch { + # Ignore cleanup errors + } + } + + It 'Should switch to specified context' { + { Switch-PowerShellGalleryContext -ID $script:SwitchTestContextName1 } | Should -Not -Throw + } + + It 'Should set context as default' { + Switch-PowerShellGalleryContext -ID $script:SwitchTestContextName2 + $config = Get-PowerShellGalleryConfig + $config.DefaultContext | Should -Be $script:SwitchTestContextName2 + } + } + + Context 'Context Integration - Disconnect-PowerShellGallery' { + BeforeEach { + # Setup test context for each test + $testContextName = 'DisconnectTestContext_' + (Get-Random) + $script:DisconnectTestContextName = $testContextName + $secureApiKey = ConvertTo-SecureString -String 'test-disconnect-api-key' -AsPlainText -Force + Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent + } + + It 'Should disconnect specified context' { + { Disconnect-PowerShellGallery -Name $script:DisconnectTestContextName -Confirm:$false } | Should -Not -Throw + } + + It 'Should remove context from vault' { + Disconnect-PowerShellGallery -Name $script:DisconnectTestContextName -Confirm:$false + $context = Get-PowerShellGalleryContext -ID $script:DisconnectTestContextName -ErrorAction SilentlyContinue + $context | Should -BeNullOrEmpty + } + } + + Context 'Context Integration - Get-PowerShellGalleryAccessToken' { + BeforeAll { + # Setup test context + $testContextName = 'TokenTestContext_' + (Get-Random) + $script:TokenTestContextName = $testContextName + $script:TestApiKey = 'test-token-api-key-xyz' + $secureApiKey = ConvertTo-SecureString -String $script:TestApiKey -AsPlainText -Force + Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent + } + + AfterAll { + # Cleanup + try { + $null = Disconnect-PowerShellGallery -Name $script:TokenTestContextName -Confirm:$false -ErrorAction SilentlyContinue + } catch { + # Ignore cleanup errors + } + } + + It 'Should retrieve API key as SecureString by default' { + $token = Get-PowerShellGalleryAccessToken -Context $script:TokenTestContextName + $token | Should -Not -BeNullOrEmpty + $token | Should -BeOfType [SecureString] + } + + It 'Should retrieve API key as plain text with -AsPlainText' { + $token = Get-PowerShellGalleryAccessToken -Context $script:TokenTestContextName -AsPlainText + $token | Should -Not -BeNullOrEmpty + $token | Should -BeOfType [string] + $token | Should -Be $script:TestApiKey + } + } + + Context 'Context Integration - Test-PowerShellGalleryAccess' { + BeforeAll { + # Setup test context + $testContextName = 'AccessTestContext_' + (Get-Random) + $script:AccessTestContextName = $testContextName + $secureApiKey = ConvertTo-SecureString -String 'test-access-api-key' -AsPlainText -Force + Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent + } + + AfterAll { + # Cleanup + try { + $null = Disconnect-PowerShellGallery -Name $script:AccessTestContextName -Confirm:$false -ErrorAction SilentlyContinue + } catch { + # Ignore cleanup errors + } + } + + It 'Should return a result object' { + $result = Test-PowerShellGalleryAccess -Context $script:AccessTestContextName -ErrorAction SilentlyContinue + $result | Should -Not -BeNullOrEmpty + $result.PSObject.Properties.Name | Should -Contain 'Success' + $result.PSObject.Properties.Name | Should -Contain 'Context' + $result.PSObject.Properties.Name | Should -Contain 'ApiUrl' + } + + It 'Should accept Context parameter' { + { Test-PowerShellGalleryAccess -Context $script:AccessTestContextName -ErrorAction SilentlyContinue } | Should -Not -Throw + } + } + + Context 'Context Integration - Module Initialization' { + It 'Initialize-PowerShellGalleryConfig should initialize config' { + { Initialize-PowerShellGalleryConfig } | Should -Not -Throw + } + + It 'Get-PowerShellGalleryConfig should return config' { + $config = Get-PowerShellGalleryConfig + $config | Should -Not -BeNullOrEmpty + $config.ID | Should -Be 'Module' + } + } } From 13b24278010d905bb1894a3a6d981f73ce531794 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:43:31 +0000 Subject: [PATCH 4/8] Fix PSScriptAnalyzer linting issues Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> --- .../Auth/Context/Remove-PowerShellGalleryContext.ps1 | 4 +++- .../Auth/Context/Set-PowerShellGalleryContext.ps1 | 6 ++++-- .../public/Auth/Connect-PowerShellGallery.ps1 | 8 ++++++-- .../Auth/Context/Switch-PowerShellGalleryContext.ps1 | 11 +++++++++-- .../public/Auth/Disconnect-PowerShellGallery.ps1 | 4 ++++ .../public/Auth/Test-PowerShellGalleryAccess.ps1 | 10 +++++++--- 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 b/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 index b443d4c..27bdf94 100644 --- a/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 +++ b/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 @@ -33,7 +33,9 @@ function Remove-PowerShellGalleryContext { if ($script:PowerShellGallery.Config.DefaultContext -eq $ID) { Write-Verbose 'Clearing default context' $script:PowerShellGallery.Config.DefaultContext = $null - $null = Set-Context -ID $script:PowerShellGallery.DefaultConfig.ID -Context $script:PowerShellGallery.Config -Vault $script:PowerShellGallery.ContextVault + $configID = $script:PowerShellGallery.DefaultConfig.ID + $vault = $script:PowerShellGallery.ContextVault + $null = Set-Context -ID $configID -Context $script:PowerShellGallery.Config -Vault $vault } } } diff --git a/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 b/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 index 8c8cac3..fe44dfe 100644 --- a/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 +++ b/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 @@ -50,12 +50,14 @@ function Set-PowerShellGalleryContext { $contextObj['ID'] = $ID if ($PSCmdlet.ShouldProcess("Context [$ID]", 'Set')) { - $result = Set-Context -ID $ID -Context $contextObj -Vault $script:PowerShellGallery.ContextVault -PassThru + $vault = $script:PowerShellGallery.ContextVault + $result = Set-Context -ID $ID -Context $contextObj -Vault $vault -PassThru if ($Default) { Write-Debug "Setting [$ID] as default context" $script:PowerShellGallery.Config.DefaultContext = $ID - $null = Set-Context -ID $script:PowerShellGallery.DefaultConfig.ID -Context $script:PowerShellGallery.Config -Vault $script:PowerShellGallery.ContextVault + $configID = $script:PowerShellGallery.DefaultConfig.ID + $null = Set-Context -ID $configID -Context $script:PowerShellGallery.Config -Vault $vault } if ($PassThru) { diff --git a/src/functions/public/Auth/Connect-PowerShellGallery.ps1 b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 index 6a2e943..0d8add4 100644 --- a/src/functions/public/Auth/Connect-PowerShellGallery.ps1 +++ b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 @@ -27,6 +27,10 @@ function Connect-PowerShellGallery { 'PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'The API key is received as clear text from user input.' )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module. Consistent with GitHub module pattern.' + )] [CmdletBinding(DefaultParameterSetName = 'Interactive')] [OutputType([System.Object])] param( @@ -59,9 +63,9 @@ function Connect-PowerShellGallery { if (-not $Silent) { Write-Host '🌐 Opening PowerShell Gallery API key management page...' -ForegroundColor Cyan } - + $apiKeyUrl = 'https://www.powershellgallery.com/account/apikeys' - + # Try to open browser try { if ($IsWindows -or $PSVersionTable.PSVersion.Major -le 5) { diff --git a/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 b/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 index a7fa5fa..3eff55d 100644 --- a/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 +++ b/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 @@ -11,6 +11,10 @@ function Switch-PowerShellGalleryContext { Switches the default PowerShell Gallery context to 'MyAccount'. #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module. Consistent with GitHub module pattern.' + )] [OutputType([void])] [CmdletBinding(SupportsShouldProcess)] param( @@ -28,14 +32,17 @@ function Switch-PowerShellGalleryContext { # Verify the context exists $context = Get-Context -ID $ID -Vault $script:PowerShellGallery.ContextVault if (-not $context) { - Write-Error "Context [$ID] not found. Use 'Get-PowerShellGalleryContext -ListAvailable' to see available contexts." + $msg = "Context [$ID] not found. Use 'Get-PowerShellGalleryContext -ListAvailable' to see available contexts." + Write-Error $msg return } if ($PSCmdlet.ShouldProcess("Default context to [$ID]", 'Switch')) { Write-Verbose "Switching default context to [$ID]" $script:PowerShellGallery.Config.DefaultContext = $ID - $null = Set-Context -ID $script:PowerShellGallery.DefaultConfig.ID -Context $script:PowerShellGallery.Config -Vault $script:PowerShellGallery.ContextVault + $configID = $script:PowerShellGallery.DefaultConfig.ID + $vault = $script:PowerShellGallery.ContextVault + $null = Set-Context -ID $configID -Context $script:PowerShellGallery.Config -Vault $vault Write-Host "✓ Switched to context [$ID]" -ForegroundColor Green } } diff --git a/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 b/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 index 52fd987..bc7fb1e 100644 --- a/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 +++ b/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 @@ -17,6 +17,10 @@ function Disconnect-PowerShellGallery { Removes the default context. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module. Consistent with GitHub module pattern.' + )] [OutputType([void])] param( # The name of the context to remove. If not specified, removes the default context. diff --git a/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 index 8bd32a7..0796138 100644 --- a/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 +++ b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 @@ -17,6 +17,10 @@ function Test-PowerShellGalleryAccess { Tests access using the 'MyAccount' context. #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module. Consistent with GitHub module pattern.' + )] [CmdletBinding()] [OutputType([System.Object])] param( @@ -57,15 +61,15 @@ function Test-PowerShellGalleryAccess { # The API key validation can be done by attempting to access the API try { Write-Verbose 'Testing API connectivity...' - + # Try to access the API root to validate connectivity $apiUrl = $contextObj.ApiUrl $headers = @{ 'X-NuGet-ApiKey' = $apiKey } - # Test basic API access - $response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers -ErrorAction Stop + # Test basic API access - just verify we can connect + $null = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers -ErrorAction Stop $result = [PSCustomObject]@{ Success = $true From 216148be0ef76154cfada5fafb7970a2b485445b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:46:13 +0000 Subject: [PATCH 5/8] Address code review security feedback Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> --- .../Auth/Context/Set-PowerShellGalleryContext.ps1 | 3 ++- .../public/Auth/Connect-PowerShellGallery.ps1 | 7 ++++--- .../Auth/Get-PowerShellGalleryAccessToken.ps1 | 5 +++++ .../public/Auth/Test-PowerShellGalleryAccess.ps1 | 15 ++++++++++++++- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 b/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 index fe44dfe..e1a886e 100644 --- a/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 +++ b/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 @@ -46,7 +46,8 @@ function Set-PowerShellGalleryContext { process { Write-Debug "Setting context: [$ID]" - $contextObj = @{} + $Context + # Create a copy of the context hashtable to avoid modifying the original + $contextObj = $Context.Clone() $contextObj['ID'] = $ID if ($PSCmdlet.ShouldProcess("Context [$ID]", 'Set')) { diff --git a/src/functions/public/Auth/Connect-PowerShellGallery.ps1 b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 index 0d8add4..4ed9b85 100644 --- a/src/functions/public/Auth/Connect-PowerShellGallery.ps1 +++ b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 @@ -97,13 +97,14 @@ function Connect-PowerShellGallery { return } - # Create context object + # Create context object using centralized configuration + $config = Get-PowerShellGalleryConfig $context = @{ ID = $Name Name = $Name ApiKey = $ApiKey - GalleryUrl = 'https://www.powershellgallery.com' - ApiUrl = 'https://www.powershellgallery.com/api/v2' + GalleryUrl = $config.GalleryUrl + ApiUrl = $config.ApiUrl ConnectedAt = Get-Date } diff --git a/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 b/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 index 4bf9751..19fb68c 100644 --- a/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 +++ b/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 @@ -7,6 +7,10 @@ function Get-PowerShellGalleryAccessToken { Retrieves the PowerShell Gallery API key from the specified context. Returns as SecureString by default, or as plain text with -AsPlainText. + SECURITY NOTE: Using -AsPlainText exposes the API key in plain text in memory. + This should only be used when necessary for API calls, and the plain text + value should be cleared from memory as soon as possible after use. + .EXAMPLE Get-PowerShellGalleryAccessToken @@ -16,6 +20,7 @@ function Get-PowerShellGalleryAccessToken { Get-PowerShellGalleryAccessToken -Context 'MyAccount' -AsPlainText Gets the API key from 'MyAccount' context as plain text. + WARNING: This exposes the API key in plain text - use with caution. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidUsingConvertToSecureStringWithPlainText', '', diff --git a/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 index 0796138..458ae8e 100644 --- a/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 +++ b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 @@ -62,8 +62,21 @@ function Test-PowerShellGalleryAccess { try { Write-Verbose 'Testing API connectivity...' - # Try to access the API root to validate connectivity + # Validate API URL is a PowerShell Gallery endpoint for security $apiUrl = $contextObj.ApiUrl + if ($apiUrl -notmatch '^https://.*powershellgallery\.com/') { + Write-Warning "API URL does not appear to be a PowerShell Gallery endpoint: $apiUrl" + $result = [PSCustomObject]@{ + Success = $false + Context = $contextObj.Name + ApiUrl = $apiUrl + TestedAt = Get-Date + Message = 'API URL validation failed - not a PowerShell Gallery endpoint' + ConnectedAt = $contextObj.ConnectedAt + } + return $result + } + $headers = @{ 'X-NuGet-ApiKey' = $apiKey } From f19cfc843ea663df44bcb75c44345be1c39bebed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:58:15 +0000 Subject: [PATCH 6/8] Add UTF-8 BOM encoding to new PowerShell files Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> --- src/functions/public/Auth/Connect-PowerShellGallery.ps1 | 2 +- .../public/Auth/Context/Switch-PowerShellGalleryContext.ps1 | 2 +- src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 | 2 +- src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/functions/public/Auth/Connect-PowerShellGallery.ps1 b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 index 4ed9b85..595d08a 100644 --- a/src/functions/public/Auth/Connect-PowerShellGallery.ps1 +++ b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 @@ -1,4 +1,4 @@ -function Connect-PowerShellGallery { +function Connect-PowerShellGallery { <# .SYNOPSIS Connects to the PowerShell Gallery by storing an API key context. diff --git a/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 b/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 index 3eff55d..2a8cb2d 100644 --- a/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 +++ b/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 @@ -1,4 +1,4 @@ -function Switch-PowerShellGalleryContext { +function Switch-PowerShellGalleryContext { <# .SYNOPSIS Switch the default PowerShell Gallery context. diff --git a/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 b/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 index bc7fb1e..043254b 100644 --- a/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 +++ b/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 @@ -1,4 +1,4 @@ -function Disconnect-PowerShellGallery { +function Disconnect-PowerShellGallery { <# .SYNOPSIS Disconnect from the PowerShell Gallery by removing a stored context. diff --git a/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 index 458ae8e..f81f070 100644 --- a/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 +++ b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 @@ -1,4 +1,4 @@ -function Test-PowerShellGalleryAccess { +function Test-PowerShellGalleryAccess { <# .SYNOPSIS Validates the stored API key by testing access to the PowerShell Gallery API. From ca3ab8ff4f5079d0bf15b6789dfc2933335134f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:07:15 +0000 Subject: [PATCH 7/8] Fix PSScriptAnalyzer issues in test file Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> --- tests/PowerShellGallery.Tests.ps1 | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/PowerShellGallery.Tests.ps1 b/tests/PowerShellGallery.Tests.ps1 index 99f81e1..d67e9e7 100644 --- a/tests/PowerShellGallery.Tests.ps1 +++ b/tests/PowerShellGallery.Tests.ps1 @@ -1,4 +1,10 @@ -Describe 'PowerShellGallery' { +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'Test file - converting test data to SecureString for testing purposes only.' +)] +param() + +Describe 'PowerShellGallery' { Context 'Function: Get-PSGalleryAPI' { It 'Should not throw' { { Get-PSGalleryAPI } | Should -Not -Throw @@ -27,7 +33,7 @@ try { $null = Disconnect-PowerShellGallery -Name $script:TestContextName -Confirm:$false -ErrorAction SilentlyContinue } catch { - # Ignore cleanup errors + Write-Verbose "Cleanup error ignored: $_" } } @@ -66,7 +72,7 @@ try { $null = Disconnect-PowerShellGallery -Name $script:GetTestContextName -Confirm:$false -ErrorAction SilentlyContinue } catch { - # Ignore cleanup errors + Write-Verbose "Cleanup error ignored: $_" } } @@ -95,10 +101,10 @@ $testContextName2 = 'SwitchTestContext2_' + (Get-Random) $script:SwitchTestContextName1 = $testContextName1 $script:SwitchTestContextName2 = $testContextName2 - + $secureApiKey1 = ConvertTo-SecureString -String 'test-switch-api-key-1' -AsPlainText -Force $secureApiKey2 = ConvertTo-SecureString -String 'test-switch-api-key-2' -AsPlainText -Force - + Connect-PowerShellGallery -Name $testContextName1 -ApiKey $secureApiKey1 -Silent Connect-PowerShellGallery -Name $testContextName2 -ApiKey $secureApiKey2 -Silent } @@ -109,7 +115,7 @@ $null = Disconnect-PowerShellGallery -Name $script:SwitchTestContextName1 -Confirm:$false -ErrorAction SilentlyContinue $null = Disconnect-PowerShellGallery -Name $script:SwitchTestContextName2 -Confirm:$false -ErrorAction SilentlyContinue } catch { - # Ignore cleanup errors + Write-Verbose "Cleanup error ignored: $_" } } @@ -159,7 +165,7 @@ try { $null = Disconnect-PowerShellGallery -Name $script:TokenTestContextName -Confirm:$false -ErrorAction SilentlyContinue } catch { - # Ignore cleanup errors + Write-Verbose "Cleanup error ignored: $_" } } @@ -191,7 +197,7 @@ try { $null = Disconnect-PowerShellGallery -Name $script:AccessTestContextName -Confirm:$false -ErrorAction SilentlyContinue } catch { - # Ignore cleanup errors + Write-Verbose "Cleanup error ignored: $_" } } From 2e492c9ab892d50e4ccd746e664de0746be17165 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:20:43 +0000 Subject: [PATCH 8/8] Enhance test output and fix private function access issue Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> --- tests/PowerShellGallery.Tests.ps1 | 67 ++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/tests/PowerShellGallery.Tests.ps1 b/tests/PowerShellGallery.Tests.ps1 index d67e9e7..5b647ed 100644 --- a/tests/PowerShellGallery.Tests.ps1 +++ b/tests/PowerShellGallery.Tests.ps1 @@ -2,6 +2,10 @@ 'PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Test file - converting test data to SecureString for testing purposes only.' )] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Test file - using Write-Host to provide test output visibility as requested.' +)] param() Describe 'PowerShellGallery' { @@ -26,6 +30,7 @@ Describe 'PowerShellGallery' { # Clean up any existing test contexts $testContextName = 'TestContext_' + (Get-Random) $script:TestContextName = $testContextName + Write-Host "Testing Connect-PowerShellGallery with context name: $testContextName" -ForegroundColor Cyan } AfterAll { @@ -37,22 +42,28 @@ Describe 'PowerShellGallery' { } } - It 'Should accept Name parameter' { + It 'Should accept Name parameter and connect successfully' { + Write-Host " → Connecting to PowerShell Gallery with Name parameter" -ForegroundColor Gray $secureApiKey = ConvertTo-SecureString -String 'test-api-key-123' -AsPlainText -Force { Connect-PowerShellGallery -Name $script:TestContextName -ApiKey $secureApiKey -Silent } | Should -Not -Throw + Write-Host " ✓ Connection succeeded" -ForegroundColor Green } - It 'Should create a context' { + It 'Should create a context with correct properties' { + Write-Host " → Retrieving context by ID: $($script:TestContextName)" -ForegroundColor Gray $context = Get-PowerShellGalleryContext -ID $script:TestContextName $context | Should -Not -BeNullOrEmpty $context.Name | Should -Be $script:TestContextName + Write-Host " ✓ Context retrieved with Name: $($context.Name), ID: $($context.ID)" -ForegroundColor Green } - It 'Should have PassThru parameter' { + It 'Should have PassThru parameter and return context' { + Write-Host " → Testing PassThru parameter" -ForegroundColor Gray $secureApiKey = ConvertTo-SecureString -String 'test-api-key-456' -AsPlainText -Force $context = Connect-PowerShellGallery -Name "$($script:TestContextName)_2" -ApiKey $secureApiKey -Silent -PassThru $context | Should -Not -BeNullOrEmpty $context.Name | Should -Be "$($script:TestContextName)_2" + Write-Host " ✓ PassThru returned context: $($context.Name)" -ForegroundColor Green # Cleanup $null = Disconnect-PowerShellGallery -Name "$($script:TestContextName)_2" -Confirm:$false -ErrorAction SilentlyContinue } @@ -63,6 +74,7 @@ Describe 'PowerShellGallery' { # Setup test context $testContextName = 'GetTestContext_' + (Get-Random) $script:GetTestContextName = $testContextName + Write-Host "Testing Get-PowerShellGalleryContext with context name: $testContextName" -ForegroundColor Cyan $secureApiKey = ConvertTo-SecureString -String 'test-get-api-key' -AsPlainText -Force Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent } @@ -77,20 +89,26 @@ Describe 'PowerShellGallery' { } It 'Should retrieve context by ID' { + Write-Host " → Retrieving context by ID: $($script:GetTestContextName)" -ForegroundColor Gray $context = Get-PowerShellGalleryContext -ID $script:GetTestContextName $context | Should -Not -BeNullOrEmpty $context.Name | Should -Be $script:GetTestContextName + Write-Host " ✓ Retrieved context: $($context.Name)" -ForegroundColor Green } It 'Should list available contexts with -ListAvailable' { + Write-Host " → Listing all available contexts" -ForegroundColor Gray $contexts = Get-PowerShellGalleryContext -ListAvailable $contexts | Should -Not -BeNullOrEmpty $contexts.Name | Should -Contain $script:GetTestContextName + Write-Host " ✓ Found $($contexts.Count) context(s), including: $($script:GetTestContextName)" -ForegroundColor Green } It 'Should return default context when no parameters provided' { + Write-Host " → Getting default context" -ForegroundColor Gray # This will either return a context or warn about no default { Get-PowerShellGalleryContext } | Should -Not -Throw + Write-Host " ✓ Default context query succeeded" -ForegroundColor Green } } @@ -101,6 +119,7 @@ Describe 'PowerShellGallery' { $testContextName2 = 'SwitchTestContext2_' + (Get-Random) $script:SwitchTestContextName1 = $testContextName1 $script:SwitchTestContextName2 = $testContextName2 + Write-Host "Testing Switch-PowerShellGalleryContext with contexts: $testContextName1, $testContextName2" -ForegroundColor Cyan $secureApiKey1 = ConvertTo-SecureString -String 'test-switch-api-key-1' -AsPlainText -Force $secureApiKey2 = ConvertTo-SecureString -String 'test-switch-api-key-2' -AsPlainText -Force @@ -120,13 +139,21 @@ Describe 'PowerShellGallery' { } It 'Should switch to specified context' { + Write-Host " → Switching to context: $($script:SwitchTestContextName1)" -ForegroundColor Gray { Switch-PowerShellGalleryContext -ID $script:SwitchTestContextName1 } | Should -Not -Throw + Write-Host " ✓ Switched successfully" -ForegroundColor Green } - It 'Should set context as default' { + It 'Should set context as default and retrieve it' { + Write-Host " → Switching to context: $($script:SwitchTestContextName2)" -ForegroundColor Gray Switch-PowerShellGalleryContext -ID $script:SwitchTestContextName2 - $config = Get-PowerShellGalleryConfig - $config.DefaultContext | Should -Be $script:SwitchTestContextName2 + + # Verify by getting the default context + Write-Host " → Verifying default context is set correctly" -ForegroundColor Gray + $defaultContext = Get-PowerShellGalleryContext + $defaultContext | Should -Not -BeNullOrEmpty + $defaultContext.ID | Should -Be $script:SwitchTestContextName2 + Write-Host " ✓ Default context verified: $($defaultContext.ID)" -ForegroundColor Green } } @@ -135,18 +162,23 @@ Describe 'PowerShellGallery' { # Setup test context for each test $testContextName = 'DisconnectTestContext_' + (Get-Random) $script:DisconnectTestContextName = $testContextName + Write-Host " → Creating test context: $testContextName" -ForegroundColor Gray $secureApiKey = ConvertTo-SecureString -String 'test-disconnect-api-key' -AsPlainText -Force Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent } It 'Should disconnect specified context' { + Write-Host " → Disconnecting context: $($script:DisconnectTestContextName)" -ForegroundColor Gray { Disconnect-PowerShellGallery -Name $script:DisconnectTestContextName -Confirm:$false } | Should -Not -Throw + Write-Host " ✓ Disconnected successfully" -ForegroundColor Green } It 'Should remove context from vault' { + Write-Host " → Disconnecting and verifying removal: $($script:DisconnectTestContextName)" -ForegroundColor Gray Disconnect-PowerShellGallery -Name $script:DisconnectTestContextName -Confirm:$false $context = Get-PowerShellGalleryContext -ID $script:DisconnectTestContextName -ErrorAction SilentlyContinue $context | Should -BeNullOrEmpty + Write-Host " ✓ Context removed from vault" -ForegroundColor Green } } @@ -156,6 +188,7 @@ Describe 'PowerShellGallery' { $testContextName = 'TokenTestContext_' + (Get-Random) $script:TokenTestContextName = $testContextName $script:TestApiKey = 'test-token-api-key-xyz' + Write-Host "Testing Get-PowerShellGalleryAccessToken with context: $testContextName" -ForegroundColor Cyan $secureApiKey = ConvertTo-SecureString -String $script:TestApiKey -AsPlainText -Force Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent } @@ -170,16 +203,20 @@ Describe 'PowerShellGallery' { } It 'Should retrieve API key as SecureString by default' { + Write-Host " → Retrieving API key as SecureString" -ForegroundColor Gray $token = Get-PowerShellGalleryAccessToken -Context $script:TokenTestContextName $token | Should -Not -BeNullOrEmpty $token | Should -BeOfType [SecureString] + Write-Host " ✓ Retrieved API key as SecureString" -ForegroundColor Green } It 'Should retrieve API key as plain text with -AsPlainText' { + Write-Host " → Retrieving API key as plain text" -ForegroundColor Gray $token = Get-PowerShellGalleryAccessToken -Context $script:TokenTestContextName -AsPlainText $token | Should -Not -BeNullOrEmpty $token | Should -BeOfType [string] $token | Should -Be $script:TestApiKey + Write-Host " ✓ Retrieved API key as plain text and verified value" -ForegroundColor Green } } @@ -188,6 +225,7 @@ Describe 'PowerShellGallery' { # Setup test context $testContextName = 'AccessTestContext_' + (Get-Random) $script:AccessTestContextName = $testContextName + Write-Host "Testing Test-PowerShellGalleryAccess with context: $testContextName" -ForegroundColor Cyan $secureApiKey = ConvertTo-SecureString -String 'test-access-api-key' -AsPlainText -Force Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent } @@ -201,28 +239,29 @@ Describe 'PowerShellGallery' { } } - It 'Should return a result object' { + It 'Should return a result object with expected properties' { + Write-Host " → Testing API access and retrieving result object" -ForegroundColor Gray $result = Test-PowerShellGalleryAccess -Context $script:AccessTestContextName -ErrorAction SilentlyContinue $result | Should -Not -BeNullOrEmpty $result.PSObject.Properties.Name | Should -Contain 'Success' $result.PSObject.Properties.Name | Should -Contain 'Context' $result.PSObject.Properties.Name | Should -Contain 'ApiUrl' + $successMsg = "Result object contains Success=$($result.Success), Context=$($result.Context)" + Write-Host " ✓ $successMsg" -ForegroundColor Green } It 'Should accept Context parameter' { + Write-Host " → Testing with explicit Context parameter" -ForegroundColor Gray { Test-PowerShellGalleryAccess -Context $script:AccessTestContextName -ErrorAction SilentlyContinue } | Should -Not -Throw + Write-Host " ✓ Context parameter accepted" -ForegroundColor Green } } Context 'Context Integration - Module Initialization' { - It 'Initialize-PowerShellGalleryConfig should initialize config' { + It 'Initialize-PowerShellGalleryConfig should initialize without error' { + Write-Host " → Testing module initialization" -ForegroundColor Gray { Initialize-PowerShellGalleryConfig } | Should -Not -Throw - } - - It 'Get-PowerShellGalleryConfig should return config' { - $config = Get-PowerShellGalleryConfig - $config | Should -Not -BeNullOrEmpty - $config.ID | Should -Be 'Module' + Write-Host " ✓ Module initialized successfully" -ForegroundColor Green } } }