diff --git a/CHANGELOG/preview.md b/CHANGELOG/preview.md index a436c10bb..a0d6685f5 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1,5 +1,10 @@ # Preview Changelog +## [Unreleased] + +### New Features +- New cmdlet `Reset-PSResourceRepository` which creates a fresh repository store by deleting the existing PSResourceRepository.xml file and registering only PSGallery with default settings + ## [1.2.0-preview3](https://github.com/PowerShell/PSResourceGet/compare/v1.2.0-preview2..v1.2.0-preview3) - 2025-09-12 ### New Features diff --git a/src/Microsoft.PowerShell.PSResourceGet.psd1 b/src/Microsoft.PowerShell.PSResourceGet.psd1 index fad665d1c..6750a4cb9 100644 --- a/src/Microsoft.PowerShell.PSResourceGet.psd1 +++ b/src/Microsoft.PowerShell.PSResourceGet.psd1 @@ -23,6 +23,7 @@ 'Get-PSScriptFileInfo', 'Install-PSResource', 'Register-PSResourceRepository', + 'Reset-PSResourceRepository', 'Save-PSResource', 'Set-PSResourceRepository', 'New-PSScriptFileInfo', diff --git a/src/code/RepositorySettings.cs b/src/code/RepositorySettings.cs index 39f129006..1d18100f9 100644 --- a/src/code/RepositorySettings.cs +++ b/src/code/RepositorySettings.cs @@ -72,7 +72,7 @@ public static void CheckRepositoryStore() } catch (Exception e) { - throw new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Repository store may be corrupted, file reading failed with error: {0}.", e.Message)); + throw new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Repository store may be corrupted, file reading failed with error: {0}. Run 'Reset-PSResourceRepository' to reset the repository store to its default state.", e.Message)); } } @@ -845,6 +845,55 @@ public static List Read(string[] repoNames, out string[] error return reposToReturn.ToList(); } + /// + /// Resets the repository store by deleting the existing file and creating a new one with PSGallery + /// Returns: PSRepositoryInfo for the newly registered PSGallery + /// + public static PSRepositoryInfo ResetRepositoryStore() + { + // Delete the existing repository file if it exists + if (File.Exists(FullRepositoryPath)) + { + try + { + File.Delete(FullRepositoryPath); + } + catch (Exception e) + { + throw new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Failed to delete repository store file with error: {0}.", e.Message)); + } + } + + // Ensure directory exists + if (!Directory.Exists(RepositoryPath)) + { + try + { + Directory.CreateDirectory(RepositoryPath); + } + catch (Exception e) + { + throw new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Failed to create repository directory with error: {0}.", e.Message)); + } + } + + // Create a new repository store file + try + { + XDocument newRepoXML = new XDocument( + new XElement("configuration")); + newRepoXML.Save(FullRepositoryPath); + } + catch (Exception e) + { + throw new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Repository store creation failed with error: {0}.", e.Message)); + } + + // Add PSGallery to the newly created store + Uri psGalleryUri = new Uri(PSGalleryRepoUri); + return Add(PSGalleryRepoName, psGalleryUri, DefaultPriority, DefaultTrusted, repoCredentialInfo: null, repoCredentialProvider: CredentialProviderType.None, APIVersion.V2, force: false); + } + #endregion #region Private methods diff --git a/src/code/ResetPSResourceRepository.cs b/src/code/ResetPSResourceRepository.cs new file mode 100644 index 000000000..cd669d0c0 --- /dev/null +++ b/src/code/ResetPSResourceRepository.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.PowerShell.PSResourceGet.UtilClasses; +using System; +using System.Management.Automation; + +namespace Microsoft.PowerShell.PSResourceGet.Cmdlets +{ + /// + /// The Reset-PSResourceRepository cmdlet creates a fresh repository store by deleting + /// the existing PSResourceRepository.xml file and creating a new one with only PSGallery registered. + /// This is useful when the repository store becomes corrupted. + /// + [Cmdlet(VerbsCommon.Reset, + "PSResourceRepository", + SupportsShouldProcess = true, + ConfirmImpact = ConfirmImpact.High)] + [OutputType(typeof(PSRepositoryInfo))] + public sealed class ResetPSResourceRepository : PSCmdlet + { + #region Parameters + + /// + /// When specified, displays the PSGallery repository that was registered. + /// + [Parameter] + public SwitchParameter PassThru { get; set; } + + #endregion + + #region Method overrides + + protected override void ProcessRecord() + { + string repositoryStorePath = System.IO.Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "PSResourceGet", + "PSResourceRepository.xml"); + + string actionMessage = $"Reset repository store at '{repositoryStorePath}'. This will delete all registered repositories and register only PSGallery."; + + if (!ShouldProcess(repositoryStorePath, actionMessage)) + { + return; + } + + try + { + WriteVerbose("Resetting repository store"); + PSRepositoryInfo psGallery = RepositorySettings.ResetRepositoryStore(); + WriteVerbose($"Repository store has been reset. PSGallery registered at: {repositoryStorePath}"); + + if (PassThru) + { + WriteObject(psGallery); + } + } + catch (Exception e) + { + ThrowTerminatingError(new ErrorRecord( + new PSInvalidOperationException($"Failed to reset repository store: {e.Message}", e), + "ResetRepositoryStoreFailed", + ErrorCategory.InvalidOperation, + this)); + } + } + + #endregion + } +} diff --git a/test/ResourceRepositoryTests/ResetPSResourceRepository.Tests.ps1 b/test/ResourceRepositoryTests/ResetPSResourceRepository.Tests.ps1 new file mode 100644 index 000000000..c64302bda --- /dev/null +++ b/test/ResourceRepositoryTests/ResetPSResourceRepository.Tests.ps1 @@ -0,0 +1,129 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +$modPath = "$psscriptroot/../PSGetTestUtils.psm1" +Write-Verbose -Verbose -Message "PSGetTestUtils path: $modPath" +Import-Module $modPath -Force -Verbose + +Describe "Test Reset-PSResourceRepository" -tags 'CI' { + BeforeEach { + $PSGalleryName = Get-PSGalleryName + $PSGalleryUri = Get-PSGalleryLocation + Get-NewPSResourceRepositoryFile + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path) + Get-NewTestDirs($tmpDirPaths) + } + AfterEach { + Get-RevertPSResourceRepositoryFile + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path) + Get-RemoveTestDirs($tmpDirPaths) + } + + It "reset repository store with multiple repositories registered" { + # Register multiple repositories + Register-PSResourceRepository -Name "testRepository1" -Uri $tmpDir1Path + Register-PSResourceRepository -Name "testRepository2" -Uri $tmpDir2Path + + # Verify multiple repositories exist + $repos = Get-PSResourceRepository + $repos.Count | Should -BeGreaterThan 2 + + # Reset repository store + Reset-PSResourceRepository -Confirm:$false + + # Verify only PSGallery exists + $repos = Get-PSResourceRepository + $repos.Count | Should -Be 1 + $repos.Name | Should -Be $PSGalleryName + $repos.Uri | Should -Be $PSGalleryUri + $repos.Priority | Should -Be 50 + $repos.Trusted | Should -Be $false + } + + It "reset repository store when only PSGallery is registered" { + # Reset repository store + Reset-PSResourceRepository -Confirm:$false + + # Verify only PSGallery exists + $repos = Get-PSResourceRepository + $repos.Count | Should -Be 1 + $repos.Name | Should -Be $PSGalleryName + } + + It "reset repository store with -PassThru returns PSGallery" { + # Register a test repository + Register-PSResourceRepository -Name "testRepository1" -Uri $tmpDir1Path + + # Reset repository store with PassThru + $result = Reset-PSResourceRepository -Confirm:$false -PassThru + + # Verify PassThru returns PSGallery + $result.Name | Should -Be $PSGalleryName + $result.Uri | Should -Be $PSGalleryUri + $result.Priority | Should -Be 50 + $result.Trusted | Should -Be $false + + # Verify only PSGallery exists + $repos = Get-PSResourceRepository + $repos.Count | Should -Be 1 + } + + It "reset repository store respects -WhatIf" { + # Register test repositories + Register-PSResourceRepository -Name "testRepository1" -Uri $tmpDir1Path + Register-PSResourceRepository -Name "testRepository2" -Uri $tmpDir2Path + + # Get count before reset + $reposBefore = Get-PSResourceRepository + $countBefore = $reposBefore.Count + + # Reset with WhatIf + Reset-PSResourceRepository -WhatIf + + # Verify repositories still exist (WhatIf should not make changes) + $reposAfter = Get-PSResourceRepository + $reposAfter.Count | Should -Be $countBefore + $reposAfter.Name | Should -Contain "testRepository1" + $reposAfter.Name | Should -Contain "testRepository2" + } + + It "reset repository store when PSGallery was unregistered" { + # Unregister PSGallery + Unregister-PSResourceRepository -Name $PSGalleryName + + # Verify PSGallery is not registered + $repos = Get-PSResourceRepository -ErrorAction SilentlyContinue + $repos.Name | Should -Not -Contain $PSGalleryName + + # Reset repository store + Reset-PSResourceRepository -Confirm:$false + + # Verify PSGallery is back + $repos = Get-PSResourceRepository + $repos.Count | Should -Be 1 + $repos.Name | Should -Be $PSGalleryName + } + + It "reset repository store with custom PSGallery settings" { + # Unregister default PSGallery and register with custom settings + Unregister-PSResourceRepository -Name $PSGalleryName + Register-PSResourceRepository -PSGallery -Priority 10 -Trusted + + # Verify custom settings + $repo = Get-PSResourceRepository -Name $PSGalleryName + $repo.Priority | Should -Be 10 + $repo.Trusted | Should -Be $true + + # Reset repository store + Reset-PSResourceRepository -Confirm:$false + + # Verify PSGallery is reset to default settings + $repo = Get-PSResourceRepository -Name $PSGalleryName + $repo.Priority | Should -Be 50 + $repo.Trusted | Should -Be $false + } +}