From da30f457d8cb40f2ab78826618b7893c073979cd Mon Sep 17 00:00:00 2001 From: annavied_microsoft Date: Mon, 12 Jan 2026 16:40:11 -0500 Subject: [PATCH 1/2] Local repository network connection failure should not fail silently --- src/code/LocalServerApiCalls.cs | 131 ++++++++++++++++-- .../FindPSResourceLocal.Tests.ps1 | 19 +++ .../InstallPSResourceLocal.Tests.ps1 | 19 +++ 3 files changed, 160 insertions(+), 9 deletions(-) diff --git a/src/code/LocalServerApiCalls.cs b/src/code/LocalServerApiCalls.cs index cc43c340d..8c3cb2e88 100644 --- a/src/code/LocalServerApiCalls.cs +++ b/src/code/LocalServerApiCalls.cs @@ -263,7 +263,24 @@ private FindResults FindNameHelper(string packageName, string[] tags, bool inclu string regexPattern = $"{packageName}" + @"(\.\d+){1,3}(?:[a-zA-Z0-9-.]+|.\d)?\.nupkg"; _cmdletPassedIn.WriteDebug($"package file name pattern to be searched for is: {regexPattern}"); - foreach (string path in Directory.GetFiles(Repository.Uri.LocalPath)) + string[] foundFiles = Utils.EmptyStrArray; + try + { + foundFiles = Directory.GetFiles(Repository.Uri.LocalPath); + } + catch (Exception e) + { + _cmdletPassedIn.WriteWarning($"Unable to resolve repository source '{Repository.Uri.LocalPath}' due to exception: {e.Message}"); + errRecord = new ErrorRecord( + exception: e, + "FileAccessFailure", + ErrorCategory.ReadError, + this); + + return findResponse; + } + + foreach (string path in foundFiles) { string packageFullName = Path.GetFileName(path); bool isMatch = Regex.IsMatch(packageFullName, regexPattern, RegexOptions.IgnoreCase); @@ -333,7 +350,12 @@ private FindResults FindNameGlobbingHelper(string packageName, string[] tags, bo List pkgsFound = new List(); errRecord = null; - Hashtable pkgVersionsFound = GetMatchingFilesGivenNamePattern(packageNameWithWildcard: packageName, includePrerelease: includePrerelease); + Hashtable pkgVersionsFound = GetMatchingFilesGivenNamePattern(packageNameWithWildcard: packageName, includePrerelease: includePrerelease, out errRecord); + if (errRecord != null) + { + // ErrorRecord errRecord is only set if directory access to retrieve files failed (i.e network error when accessing file share, incorrect directory path, etc), not if desired files within directory are not found since this is a wildcard scenario. + return findResponse; + } List pkgNamesList = pkgVersionsFound.Keys.Cast().ToList(); foreach(string pkgFound in pkgNamesList) @@ -382,7 +404,24 @@ private FindResults FindVersionHelper(string packageName, string version, string string pkgPath = String.Empty; string actualPkgName = String.Empty; - foreach (string path in Directory.GetFiles(Repository.Uri.LocalPath)) + string[] foundFiles = Utils.EmptyStrArray; + try + { + foundFiles = Directory.GetFiles(Repository.Uri.LocalPath); + } + catch (Exception e) + { + _cmdletPassedIn.WriteWarning($"Unable to resolve repository source '{Repository.Uri.LocalPath}' due to exception: {e.Message}"); + errRecord = new ErrorRecord( + exception: e, + "FileAccessFailure", + ErrorCategory.ReadError, + this); + + return findResponse; + } + + foreach (string path in foundFiles) { string packageFullName = Path.GetFileName(path); bool isMatch = Regex.IsMatch(packageFullName, regexPattern, RegexOptions.IgnoreCase); @@ -450,7 +489,12 @@ private FindResults FindTagsHelper(string[] tags, bool includePrerelease, out Er List pkgsFound = new List(); errRecord = null; - Hashtable pkgVersionsFound = GetMatchingFilesGivenNamePattern(packageNameWithWildcard: String.Empty, includePrerelease: includePrerelease); + Hashtable pkgVersionsFound = GetMatchingFilesGivenNamePattern(packageNameWithWildcard: String.Empty, includePrerelease: includePrerelease, errRecord: out errRecord); + if (errRecord != null) + { + // ErrorRecord errRecord is only set if directory access to retrieve files failed (i.e network error when accessing file share, incorrect directory path, etc), not if desired files within directory are not found since this is a wildcard scenario. + return findResponse; + } List pkgNamesList = pkgVersionsFound.Keys.Cast().ToList(); foreach(string pkgFound in pkgNamesList) @@ -496,7 +540,24 @@ private Stream InstallName(string packageName, bool includePrerelease, out Error NuGetVersion latestVersion = new NuGetVersion("0.0.0.0"); String latestVersionPath = String.Empty; - foreach (string path in Directory.GetFiles(Repository.Uri.LocalPath)) + string[] foundFiles = Utils.EmptyStrArray; + try + { + foundFiles = Directory.GetFiles(Repository.Uri.LocalPath); + } + catch (Exception e) + { + _cmdletPassedIn.WriteWarning($"Unable to resolve repository source '{Repository.Uri.LocalPath}' due to exception: {e.Message}"); + errRecord = new ErrorRecord( + exception: e, + "FileAccessFailure", + ErrorCategory.ReadError, + this); + + return fs; + } + + foreach (string path in foundFiles) { string packageFullName = Path.GetFileName(path); @@ -581,7 +642,24 @@ private Stream InstallVersion(string packageName, string version, out ErrorRecor WildcardPattern pkgNamePattern = new WildcardPattern($"{packageName}.{version}.nupkg*", WildcardOptions.IgnoreCase); String pkgVersionPath = String.Empty; - foreach (string path in Directory.GetFiles(Repository.Uri.LocalPath)) + string[] foundFiles = Utils.EmptyStrArray; + try + { + foundFiles = Directory.GetFiles(Repository.Uri.LocalPath); + } + catch (Exception e) + { + _cmdletPassedIn.WriteWarning($"Unable to resolve repository source '{Repository.Uri.LocalPath}' due to exception: {e.Message}"); + errRecord = new ErrorRecord( + exception: e, + "FileAccessFailure", + ErrorCategory.ReadError, + this); + + return fs; + } + + foreach (string path in foundFiles) { string packageFullName = Path.GetFileName(path); @@ -778,7 +856,24 @@ private Hashtable GetMatchingFilesGivenSpecificName(string packageName, bool inc Hashtable pkgVersionsFound = new Hashtable(StringComparer.OrdinalIgnoreCase); errRecord = null; - foreach (string path in Directory.GetFiles(Repository.Uri.LocalPath)) + string[] foundFiles = Utils.EmptyStrArray; + try + { + foundFiles = Directory.GetFiles(Repository.Uri.LocalPath); + } + catch (Exception e) + { + _cmdletPassedIn.WriteWarning($"Unable to resolve repository source '{Repository.Uri.LocalPath}' due to exception: {e.Message}"); + errRecord = new ErrorRecord( + exception: e, + "FileAccessFailure", + ErrorCategory.ReadError, + this); + + return pkgVersionsFound; + } + + foreach (string path in foundFiles) { string packageFullName = Path.GetFileName(path); @@ -809,9 +904,10 @@ private Hashtable GetMatchingFilesGivenSpecificName(string packageName, bool inc /// hashtable with those that match the name wildcard pattern and prerelease requirements provided. /// This helper method is called for FindAll(), FindTags(), FindNameGlobbing() scenarios. /// - private Hashtable GetMatchingFilesGivenNamePattern(string packageNameWithWildcard, bool includePrerelease) + private Hashtable GetMatchingFilesGivenNamePattern(string packageNameWithWildcard, bool includePrerelease, out ErrorRecord errRecord) { _cmdletPassedIn.WriteDebug("In LocalServerApiCalls::GetMatchingFilesGivenNamePattern()"); + errRecord = null; bool isNameFilteringRequired = !String.IsNullOrEmpty(packageNameWithWildcard); // wildcard name possibilities: power*, *get, power*get @@ -820,7 +916,24 @@ private Hashtable GetMatchingFilesGivenNamePattern(string packageNameWithWildcar Regex rx = new Regex(@"\.\d+\.", RegexOptions.Compiled | RegexOptions.IgnoreCase); Hashtable pkgVersionsFound = new Hashtable(StringComparer.OrdinalIgnoreCase); - foreach (string path in Directory.GetFiles(Repository.Uri.LocalPath)) + string[] foundFiles = Utils.EmptyStrArray; + try + { + foundFiles = Directory.GetFiles(Repository.Uri.LocalPath); + } + catch (Exception e) + { + _cmdletPassedIn.WriteWarning($"Unable to resolve repository source '{Repository.Uri.LocalPath}' due to exception: {e.Message}"); + errRecord = new ErrorRecord( + exception: e, + "FileAccessFailure", + ErrorCategory.ReadError, + this); + + return pkgVersionsFound; + } + + foreach (string path in foundFiles) { string packageFullName = Path.GetFileName(path); MatchCollection matches = rx.Matches(packageFullName); diff --git a/test/FindPSResourceTests/FindPSResourceLocal.Tests.ps1 b/test/FindPSResourceTests/FindPSResourceLocal.Tests.ps1 index a3cbe2335..961e975a9 100644 --- a/test/FindPSResourceTests/FindPSResourceLocal.Tests.ps1 +++ b/test/FindPSResourceTests/FindPSResourceLocal.Tests.ps1 @@ -12,6 +12,7 @@ Describe 'Test Find-PSResource for local repositories' -tags 'CI' { BeforeAll{ $localRepo = "psgettestlocal" $localUNCRepo = 'psgettestlocal3' + $localPrivateRepo = "psgettestlocal5" $testModuleName = "test_local_mod" $testModuleName2 = "test_local_mod2" $testModuleName3 = "Test_Local_Mod3" @@ -347,4 +348,22 @@ Describe 'Test Find-PSResource for local repositories' -tags 'CI' { $res = Find-PSResource -Name 'Az.KeyVault' -Repository $localRepo $res.Version | Should -Be "6.3.1" } + + It "Find should not silently fail if network connection to local private repository cannot be established and remainder repositories should be searched" { + $privateRepo = Get-PSResourceRepository $localPrivateRepo + $res = Find-PSResource -Name $testModuleName -WarningVariable WarningVar -WarningAction SilentlyContinue + $WarningVar | Should -Not -BeNullOrEmpty + $WarningVar[0] | Should -Match "$($privateRepo.Uri.LocalPath)" + $res.Name | Should -Contain $testModuleName + $res.Version | Should -Be "1.0.0" + } + + It "Find should not silently fail if network connection to local private repository cannot be established and package version was provided and remainder repositories should be searched" { + $privateRepo = Get-PSResourceRepository $localPrivateRepo + $res = Find-PSResource -Name $testModuleName -Version "1.0.0" -WarningVariable WarningVar -WarningAction SilentlyContinue + $WarningVar | Should -Not -BeNullOrEmpty + $WarningVar[0] | Should -Match "$($privateRepo.Uri.LocalPath)" + $res.Name | Should -Contain $testModuleName + $res.Version | Should -Be "1.0.0" + } } diff --git a/test/InstallPSResourceTests/InstallPSResourceLocal.Tests.ps1 b/test/InstallPSResourceTests/InstallPSResourceLocal.Tests.ps1 index 77c8766ad..030d43ddf 100644 --- a/test/InstallPSResourceTests/InstallPSResourceLocal.Tests.ps1 +++ b/test/InstallPSResourceTests/InstallPSResourceLocal.Tests.ps1 @@ -16,6 +16,7 @@ Describe 'Test Install-PSResource for local repositories' -tags 'CI' { BeforeAll { $localRepo = "psgettestlocal" $localUNCRepo = "psgettestlocal3" + $localPrivateRepo = "psgettestlocal5" $localNupkgRepo = "localNupkgRepo" $testModuleName = "test_local_mod" $testModuleName2 = "test_local_mod2" @@ -296,4 +297,22 @@ Describe 'Test Install-PSResource for local repositories' -tags 'CI' { $pkg.Name | Should -Be $nupkgName $pkg.Version | Should -Be $nupkgVersion } + + It "Install should not silently fail if network connection to local private repository cannot be established and remainder repositories should be searched" { + $privateRepo = Get-PSResourceRepository $localPrivateRepo + $res = Install-PSResource -Name $testModuleName -TrustRepository -PassThru -WarningVariable WarningVar -WarningAction SilentlyContinue + $WarningVar | Should -Not -BeNullOrEmpty + $WarningVar[0] | Should -Match "$($privateRepo.Uri.LocalPath)" + $res.Name | Should -Contain $testModuleName + $res.Version | Should -Be "1.0.0" + } + + It "Install should not silently fail if network connection to local private repository cannot be established and package version was provided and remainder repositories should be searched" { + $privateRepo = Get-PSResourceRepository $localPrivateRepo + $res = Install-PSResource -Name $testModuleName -Version "1.0.0" -TrustRepository -PassThru -WarningVariable WarningVar -WarningAction SilentlyContinue + $WarningVar | Should -Not -BeNullOrEmpty + $WarningVar[0] | Should -Match "$($privateRepo.Uri.LocalPath)" + $res.Name | Should -Contain $testModuleName + $res.Version | Should -Be "1.0.0" + } } From 29baaa167898450e210b49db20fc1a12bb3c1311 Mon Sep 17 00:00:00 2001 From: annavied_microsoft Date: Tue, 13 Jan 2026 13:42:14 -0500 Subject: [PATCH 2/2] register local repo with private network --- test/PSGetTestUtils.psm1 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/PSGetTestUtils.psm1 b/test/PSGetTestUtils.psm1 index 6a384c17c..27809fabe 100644 --- a/test/PSGetTestUtils.psm1 +++ b/test/PSGetTestUtils.psm1 @@ -269,8 +269,17 @@ function Register-LocalRepos { Trusted = $false } Register-PSResourceRepository @localRepoParams2 + + $path4 = "\\localhost\PSRepoLocal" + $localRepoParams2 = @{ + Name = "psgettestlocal5" + Uri = $path4 + Priority = 30 + Trusted = $false + } + Register-PSResourceRepository @localRepoParams2 - Write-Verbose "registered psgettestlocal, psgettestlocal2, psgettestlocal3, psgettestlocal4" + Write-Verbose "registered psgettestlocal, psgettestlocal2, psgettestlocal3, psgettestlocal4, psgettestlocal5" } function Register-LocalTestNupkgsRepo {