Skip to content

Commit cfa7758

Browse files
Copilotalerickson
andcommitted
Add Reset-PSResourceRepository cmdlet and Reset method to RepositorySettings
Co-authored-by: alerickson <[email protected]>
1 parent dad9e47 commit cfa7758

File tree

3 files changed

+316
-1
lines changed

3 files changed

+316
-1
lines changed

src/code/RepositorySettings.cs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public static void CheckRepositoryStore()
7272
}
7373
catch (Exception e)
7474
{
75-
throw new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Repository store may be corrupted, file reading failed with error: {0}.", e.Message));
75+
throw new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Repository store may be corrupted, file reading failed with error: {0}. Try running 'Reset-PSResourceRepository' to reset the repository store.", e.Message));
7676
}
7777
}
7878

@@ -845,6 +845,102 @@ public static List<PSRepositoryInfo> Read(string[] repoNames, out string[] error
845845
return reposToReturn.ToList();
846846
}
847847

848+
/// <summary>
849+
/// Reset the repository store by creating a new PSRepositories.xml file with PSGallery registered.
850+
/// This creates a temporary new file first, and only replaces the old file if creation succeeds.
851+
/// If creation fails, the old file is restored.
852+
/// Returns: PSRepositoryInfo for the PSGallery repository
853+
/// </summary>
854+
public static PSRepositoryInfo Reset(PSCmdlet cmdletPassedIn, out string errorMsg)
855+
{
856+
errorMsg = string.Empty;
857+
string tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".xml");
858+
string backupFilePath = string.Empty;
859+
860+
try
861+
{
862+
// Ensure the repository directory exists
863+
if (!Directory.Exists(RepositoryPath))
864+
{
865+
Directory.CreateDirectory(RepositoryPath);
866+
}
867+
868+
// Create new repository XML in a temporary location
869+
XDocument newRepoXML = new XDocument(
870+
new XElement("configuration"));
871+
newRepoXML.Save(tempFilePath);
872+
873+
// Validate that the temporary file can be loaded
874+
LoadXDocument(tempFilePath);
875+
876+
// Back up the existing file if it exists
877+
if (File.Exists(FullRepositoryPath))
878+
{
879+
backupFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + "_backup.xml");
880+
File.Copy(FullRepositoryPath, backupFilePath, overwrite: true);
881+
882+
// Delete the old file
883+
File.Delete(FullRepositoryPath);
884+
}
885+
886+
// Move the temporary file to the actual location
887+
File.Copy(tempFilePath, FullRepositoryPath, overwrite: true);
888+
889+
// Add PSGallery to the newly created store
890+
Uri psGalleryUri = new Uri(PSGalleryRepoUri);
891+
PSRepositoryInfo psGalleryRepo = Add(PSGalleryRepoName, psGalleryUri, DefaultPriority, DefaultTrusted, repoCredentialInfo: null, repoCredentialProvider: CredentialProviderType.None, APIVersion.V2, force: false);
892+
893+
// Clean up temporary and backup files on success
894+
if (File.Exists(tempFilePath))
895+
{
896+
File.Delete(tempFilePath);
897+
}
898+
if (!string.IsNullOrEmpty(backupFilePath) && File.Exists(backupFilePath))
899+
{
900+
File.Delete(backupFilePath);
901+
}
902+
903+
return psGalleryRepo;
904+
}
905+
catch (Exception e)
906+
{
907+
// Restore the backup file if it exists
908+
if (!string.IsNullOrEmpty(backupFilePath) && File.Exists(backupFilePath))
909+
{
910+
try
911+
{
912+
if (File.Exists(FullRepositoryPath))
913+
{
914+
File.Delete(FullRepositoryPath);
915+
}
916+
File.Copy(backupFilePath, FullRepositoryPath, overwrite: true);
917+
File.Delete(backupFilePath);
918+
}
919+
catch (Exception restoreEx)
920+
{
921+
errorMsg = string.Format(CultureInfo.InvariantCulture, "Repository store reset failed with error: {0}. An attempt to restore the old repository store also failed with error: {1}", e.Message, restoreEx.Message);
922+
return null;
923+
}
924+
}
925+
926+
// Clean up temporary file
927+
if (File.Exists(tempFilePath))
928+
{
929+
try
930+
{
931+
File.Delete(tempFilePath);
932+
}
933+
catch
934+
{
935+
// Ignore cleanup errors
936+
}
937+
}
938+
939+
errorMsg = string.Format(CultureInfo.InvariantCulture, "Repository store reset failed with error: {0}.", e.Message);
940+
return null;
941+
}
942+
}
943+
848944
#endregion
849945

850946
#region Private methods
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.PowerShell.PSResourceGet.UtilClasses;
5+
using System;
6+
using System.Management.Automation;
7+
8+
namespace Microsoft.PowerShell.PSResourceGet.Cmdlets
9+
{
10+
/// <summary>
11+
/// The Reset-PSResourceRepository cmdlet resets the repository store by creating a new PSRepositories.xml file.
12+
/// This is useful when the repository store becomes corrupted.
13+
/// It will create a new repository store with only the PSGallery repository registered.
14+
/// </summary>
15+
[Cmdlet(VerbsCommon.Reset,
16+
"PSResourceRepository",
17+
SupportsShouldProcess = true,
18+
ConfirmImpact = ConfirmImpact.High)]
19+
[OutputType(typeof(PSRepositoryInfo))]
20+
public sealed class ResetPSResourceRepository : PSCmdlet
21+
{
22+
#region Parameters
23+
24+
/// <summary>
25+
/// When specified, displays the PSGallery repository that was registered after reset
26+
/// </summary>
27+
[Parameter]
28+
public SwitchParameter PassThru { get; set; }
29+
30+
#endregion
31+
32+
#region Methods
33+
34+
protected override void ProcessRecord()
35+
{
36+
string repositoryStorePath = System.IO.Path.Combine(
37+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
38+
"PSResourceGet",
39+
"PSResourceRepository.xml");
40+
41+
WriteVerbose($"Resetting repository store at: {repositoryStorePath}");
42+
43+
if (!ShouldProcess(repositoryStorePath, "Reset repository store and create new PSRepositories.xml file with PSGallery registered"))
44+
{
45+
return;
46+
}
47+
48+
PSRepositoryInfo psGalleryRepo = RepositorySettings.Reset(this, out string errorMsg);
49+
50+
if (!string.IsNullOrEmpty(errorMsg))
51+
{
52+
WriteError(new ErrorRecord(
53+
new PSInvalidOperationException(errorMsg),
54+
"ErrorResettingRepositoryStore",
55+
ErrorCategory.InvalidOperation,
56+
this));
57+
return;
58+
}
59+
60+
WriteVerbose("Repository store reset successfully. PSGallery has been registered.");
61+
62+
if (PassThru && psGalleryRepo != null)
63+
{
64+
WriteObject(psGalleryRepo);
65+
}
66+
}
67+
68+
#endregion
69+
}
70+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
$modPath = "$psscriptroot/../PSGetTestUtils.psm1"
5+
Write-Verbose -Verbose -Message "PSGetTestUtils path: $modPath"
6+
Import-Module $modPath -Force -Verbose
7+
8+
Describe "Test Reset-PSResourceRepository" -tags 'CI' {
9+
BeforeEach {
10+
$PSGalleryName = Get-PSGalleryName
11+
$PSGalleryUri = Get-PSGalleryLocation
12+
Get-NewPSResourceRepositoryFile
13+
}
14+
15+
AfterEach {
16+
Get-RevertPSResourceRepositoryFile
17+
}
18+
19+
It "Reset repository store without PassThru parameter" {
20+
# Arrange: Add a test repository
21+
$TestRepoName = "testRepository"
22+
$tmpDirPath = Join-Path -Path $TestDrive -ChildPath "tmpDir1"
23+
New-Item -ItemType Directory -Path $tmpDirPath -Force | Out-Null
24+
Register-PSResourceRepository -Name $TestRepoName -Uri $tmpDirPath
25+
26+
# Verify repository was added
27+
$repos = Get-PSResourceRepository
28+
$repos.Count | Should -BeGreaterThan 1
29+
30+
# Act: Reset repository store
31+
Reset-PSResourceRepository -Confirm:$false
32+
33+
# Assert: Only PSGallery should exist
34+
$repos = Get-PSResourceRepository
35+
$repos.Count | Should -Be 1
36+
$repos.Name | Should -Be $PSGalleryName
37+
$repos.Uri | Should -Be $PSGalleryUri
38+
}
39+
40+
It "Reset repository store with PassThru parameter returns PSGallery" {
41+
# Arrange: Add a test repository
42+
$TestRepoName = "testRepository"
43+
$tmpDirPath = Join-Path -Path $TestDrive -ChildPath "tmpDir1"
44+
New-Item -ItemType Directory -Path $tmpDirPath -Force | Out-Null
45+
Register-PSResourceRepository -Name $TestRepoName -Uri $tmpDirPath
46+
47+
# Act: Reset repository store with PassThru
48+
$result = Reset-PSResourceRepository -Confirm:$false -PassThru
49+
50+
# Assert: Result should be PSGallery repository info
51+
$result | Should -Not -BeNullOrEmpty
52+
$result.Name | Should -Be $PSGalleryName
53+
$result.Uri | Should -Be $PSGalleryUri
54+
$result.Trusted | Should -Be $false
55+
$result.Priority | Should -Be 50
56+
57+
# Verify only PSGallery exists
58+
$repos = Get-PSResourceRepository
59+
$repos.Count | Should -Be 1
60+
}
61+
62+
It "Reset repository store should support -WhatIf" {
63+
# Arrange: Add a test repository
64+
$TestRepoName = "testRepository"
65+
$tmpDirPath = Join-Path -Path $TestDrive -ChildPath "tmpDir1"
66+
New-Item -ItemType Directory -Path $tmpDirPath -Force | Out-Null
67+
Register-PSResourceRepository -Name $TestRepoName -Uri $tmpDirPath
68+
69+
# Capture repository count before WhatIf
70+
$reposBefore = Get-PSResourceRepository
71+
$countBefore = $reposBefore.Count
72+
73+
# Act: Run with WhatIf
74+
Reset-PSResourceRepository -WhatIf
75+
76+
# Assert: Repositories should not have changed
77+
$reposAfter = Get-PSResourceRepository
78+
$reposAfter.Count | Should -Be $countBefore
79+
}
80+
81+
It "Reset repository store when corrupted should succeed" {
82+
# Arrange: Corrupt the repository file
83+
$powerShellGetPath = Join-Path -Path ([Environment]::GetFolderPath([System.Environment+SpecialFolder]::LocalApplicationData)) -ChildPath "PSResourceGet"
84+
$repoFilePath = Join-Path -Path $powerShellGetPath -ChildPath "PSResourceRepository.xml"
85+
86+
# Write invalid XML to corrupt the file
87+
"This is not valid XML" | Set-Content -Path $repoFilePath -Force
88+
89+
# Act: Reset the repository store
90+
$result = Reset-PSResourceRepository -Confirm:$false -PassThru
91+
92+
# Assert: Should successfully reset and return PSGallery
93+
$result | Should -Not -BeNullOrEmpty
94+
$result.Name | Should -Be $PSGalleryName
95+
96+
# Verify we can now read repositories
97+
$repos = Get-PSResourceRepository
98+
$repos.Count | Should -Be 1
99+
$repos.Name | Should -Be $PSGalleryName
100+
}
101+
102+
It "Reset repository store when file doesn't exist should succeed" {
103+
# Arrange: Delete the repository file
104+
$powerShellGetPath = Join-Path -Path ([Environment]::GetFolderPath([System.Environment+SpecialFolder]::LocalApplicationData)) -ChildPath "PSResourceGet"
105+
$repoFilePath = Join-Path -Path $powerShellGetPath -ChildPath "PSResourceRepository.xml"
106+
107+
if (Test-Path -Path $repoFilePath) {
108+
Remove-Item -Path $repoFilePath -Force
109+
}
110+
111+
# Act: Reset the repository store
112+
$result = Reset-PSResourceRepository -Confirm:$false -PassThru
113+
114+
# Assert: Should successfully reset and return PSGallery
115+
$result | Should -Not -BeNullOrEmpty
116+
$result.Name | Should -Be $PSGalleryName
117+
118+
# Verify PSGallery is registered
119+
$repos = Get-PSResourceRepository
120+
$repos.Count | Should -Be 1
121+
$repos.Name | Should -Be $PSGalleryName
122+
}
123+
124+
It "Reset repository store with multiple repositories should only keep PSGallery" {
125+
# Arrange: Register multiple repositories
126+
$tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1"
127+
$tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2"
128+
$tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3"
129+
New-Item -ItemType Directory -Path $tmpDir1Path -Force | Out-Null
130+
New-Item -ItemType Directory -Path $tmpDir2Path -Force | Out-Null
131+
New-Item -ItemType Directory -Path $tmpDir3Path -Force | Out-Null
132+
133+
Register-PSResourceRepository -Name "testRepo1" -Uri $tmpDir1Path
134+
Register-PSResourceRepository -Name "testRepo2" -Uri $tmpDir2Path
135+
Register-PSResourceRepository -Name "testRepo3" -Uri $tmpDir3Path
136+
137+
# Verify multiple repositories exist
138+
$reposBefore = Get-PSResourceRepository
139+
$reposBefore.Count | Should -BeGreaterThan 1
140+
141+
# Act: Reset repository store
142+
Reset-PSResourceRepository -Confirm:$false
143+
144+
# Assert: Only PSGallery should remain
145+
$reposAfter = Get-PSResourceRepository
146+
$reposAfter.Count | Should -Be 1
147+
$reposAfter.Name | Should -Be $PSGalleryName
148+
}
149+
}

0 commit comments

Comments
 (0)