Skip to content

Commit c4c5a15

Browse files
Merge pull request #5 from PowershellFrameworkCollective/development
0.9.16
2 parents 3f401e6 + e71ac0c commit c4c5a15

11 files changed

Lines changed: 180 additions & 43 deletions

File tree

PSFramework.NuGet/PSFramework.NuGet.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
RootModule = 'PSFramework.NuGet.psm1'
44

55
# Version number of this module.
6-
ModuleVersion = '0.9.12'
6+
ModuleVersion = '0.9.16'
77

88
# ID used to uniquely identify this module
99
GUID = 'ad0f2a25-552f-4dd6-bd8e-5ddced2a5d88'

PSFramework.NuGet/changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## 0.9.16 (2025-05-17)
4+
5+
+ Upd: Install-PSFPowerShellGet - now allows bootstrapping localhost without requiring elevation.
6+
+ Fix: Publish-PSFResourceModule - does not include files with brackets (`[]`) in their name.
7+
+ Fix: Save-PSFResourceModule - does not include empty folders or files when using V3 repositories.
8+
+ Fix: Find-PSFModule - fails (with error) when searching for prerelease versions on a default Windows PowerShell console without any modifications.
9+
310
## 0.9.12 (2025-05-06)
411

512
+ Fix: Install-PSFModule - fails to install on a default Windows PowerShell console without any modifications.

PSFramework.NuGet/en-us/strings.psd1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
'Copy-Module.Error.StagingFolderCopy' = 'Failed to deploy module to staging directory when trying to publish {0}' # $Path
99
'Copy-Module.Error.StagingFolderFailed' = 'Failed to create staging folder when trying to publish {0}' # $Path
1010

11+
'Find-PSFModule.AllowPrerease.NotSupported' = 'Cannot search for prerelease modules - PowerShellGet is too old! To fix this, use "Install-PSFPowerShellGet -Type V2Latest, V3Latest" and start a new console.' #
12+
1113
'Install-PSFModule.Error.Installation' = 'Failed to install {0}' # $Name -join ','
1214
'Install-PSFModule.Error.NoComputerValid' = 'Unable to establish ANY remote connections to {0}' # $ComputerName -join ',
1315
'Install-PSFModule.Error.Setup' = 'Failed to prepare to install {0}' # $Name -join ','
@@ -65,6 +67,7 @@
6567

6668
'Save-PowerShellGet.Error.UnableToResolve' = 'Unable to resolve aka.ms link: {0}. Make sure internet access is available!' # $link
6769

70+
'Save-PSFModule.AllowPrerease.NotSupported' = 'Cannot install prerelease modules - PowerShellGet is too old! To fix this, use "Install-PSFPowerShellGet -Type V2Latest, V3Latest" and start a new console.' #
6871
'Save-PSFModule.Error.NoComputerValid' = 'Failed to connect to any of the provided computer targets: {0}' # ($ComputerName -join ', ')
6972

7073
'Save-PSFResourceModule.Deploying' = 'Deploying {2} from resource module {0} ({1}) to {3}' # $module.Name, $versionFolder.Name, $item.Name, $pathEntry

PSFramework.NuGet/functions/Get/Find-PSFModule.ps1

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -131,52 +131,56 @@
131131
$param = $PSBoundParameters | ConvertTo-PSFHashtable -Include Name, Repository, Tag, Credential, IncludeDependencies
132132
}
133133
process {
134-
#region V2
135-
if ($script:psget.V2 -and $Type -in 'All', 'V2') {
134+
#region V3
135+
if ($script:psget.V3 -and $Type -in 'All', 'V3') {
136136
$paramClone = $param.Clone()
137-
$paramClone += $PSBoundParameters | ConvertTo-PSFHashtable -Include AllVersions, AllowPrerelease
138-
if ($Version) {
139-
if ($convertedVersion.Required) { $paramClone.RequiredVersion = $convertedVersion.Required }
140-
if ($convertedVersion.Minimum) { $paramClone.MinimumVersion = $convertedVersion.Minimum }
141-
if ($convertedVersion.Maximum) { $paramClone.MaximumVersion = $convertedVersion.Maximum }
137+
$paramClone += $PSBoundParameters | ConvertTo-PSFHashtable -Include AllowPrerelease -Remap @{
138+
AllowPrerelease = 'Prerelease'
142139
}
140+
if ($useVersionFilter) {
141+
$paramClone.Version = $versionFilter
142+
}
143+
$paramClone.Type = 'Module'
143144
$execute = $true
144145
if ($paramClone.Repository) {
145146
$paramClone.Repository = $paramClone.Repository | Where-Object {
146147
$_ -match '\*' -or
147-
$_ -in (Get-PSFRepository -Type V2).Name
148+
$_ -in (Get-PSFRepository -Type V3).Name
148149
}
149150
$execute = $paramClone.Repository -as [bool]
150151
}
151-
152152
if ($execute) {
153-
Find-Module @paramClone | ConvertFrom-ModuleInfo
153+
Find-PSResource @paramClone | ConvertFrom-ModuleInfo
154154
}
155155
}
156-
#endregion V2
156+
#endregion V3
157157

158-
#region V3
159-
if ($script:psget.V3 -and $Type -in 'All', 'V3') {
158+
#region V2
159+
if ($script:psget.V2 -and $Type -in 'All', 'V2') {
160160
$paramClone = $param.Clone()
161-
$paramClone += $PSBoundParameters | ConvertTo-PSFHashtable -Include AllowPrerelease -Remap @{
162-
AllowPrerelease = 'Prerelease'
163-
}
164-
if ($useVersionFilter) {
165-
$paramClone.Version = $versionFilter
161+
$paramClone += $PSBoundParameters | ConvertTo-PSFHashtable -Include AllVersions, AllowPrerelease
162+
if ($Version) {
163+
if ($convertedVersion.Required) { $paramClone.RequiredVersion = $convertedVersion.Required }
164+
if ($convertedVersion.Minimum) { $paramClone.MinimumVersion = $convertedVersion.Minimum }
165+
if ($convertedVersion.Maximum) { $paramClone.MaximumVersion = $convertedVersion.Maximum }
166166
}
167-
$paramClone.Type = 'Module'
168167
$execute = $true
169168
if ($paramClone.Repository) {
170169
$paramClone.Repository = $paramClone.Repository | Where-Object {
171170
$_ -match '\*' -or
172-
$_ -in (Get-PSFRepository -Type V3).Name
171+
$_ -in (Get-PSFRepository -Type V2).Name
173172
}
174173
$execute = $paramClone.Repository -as [bool]
175174
}
175+
176176
if ($execute) {
177-
Find-PSResource @paramClone | ConvertFrom-ModuleInfo
177+
$paramClone = $paramClone | ConvertTo-PSFHashtable -ReferenceCommand Find-Module
178+
if ($AllowPrerelease -and $paramClone.Keys -notcontains 'AllowPrerelease') {
179+
Write-PSFMessage -Level Warning -String 'Find-PSFModule.AllowPrerease.NotSupported' -Once 'OldPSGetV2_Prerelease'
180+
}
181+
Find-Module @paramClone | ConvertFrom-ModuleInfo
178182
}
179183
}
180-
#endregion V3
184+
#endregion V2
181185
}
182186
}

PSFramework.NuGet/functions/PowerShellGet/Install-PSFPowerShellGet.ps1

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
4343
.PARAMETER NotInternal
4444
Do not use the internally provided PowerShellGet module versions.
4545
This REQUIRES you to either provide the module data via -SourcePath or to have live online access.
46+
47+
.PARAMETER UserMode
48+
Deploy the resource into user paths, rather than computer-wide.
49+
This allows bootstrapping _without_ requiring elevation and is usually only needed on the local computer.
50+
This mode is automatically selected when deploying to the local computer and not running PowerShell "As Administrator".
51+
Only applies to / affects Windows computers.
4652
4753
.EXAMPLE
4854
PS C:\> Install-PSFPowerShell -Type V3Latest -ComputerName (Get-ADComputer -Filter * -SearchBase $myOU)
@@ -69,7 +75,10 @@
6975
$Offline,
7076

7177
[switch]
72-
$NotInternal
78+
$NotInternal,
79+
80+
[switch]
81+
$UserMode
7382
)
7483

7584
begin {
@@ -161,7 +170,7 @@
161170

162171
$actualConfiguration = Import-PSFPowerShellDataFile -Path (Join-Path -Path $rootPath -ChildPath 'modules.json')
163172
$data = @{
164-
Type = $Type
173+
Type = $Type
165174
Config = $actualConfiguration
166175
}
167176
switch ($Type) {
@@ -180,7 +189,9 @@
180189
#region Actual Code
181190
$code = {
182191
param (
183-
$Data
192+
$Data,
193+
194+
$AsCurrentUser
184195
)
185196

186197
#region Functions
@@ -233,14 +244,19 @@
233244
#region V2 Bootstrap
234245
V2Binaries {
235246
if ($isOnWindows) {
236-
if (-not (Test-Path -Path "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet")) {
237-
$null = New-Item -Path "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet" -ItemType Directory -Force
247+
$getRoot = "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet"
248+
if ($AsCurrentUser) { $getRoot = "$env:LocalAppData\Microsoft\Windows\PowerShell\PowerShellGet" }
249+
if (-not (Test-Path -Path $getRoot)) {
250+
$null = New-Item -Path $getRoot -ItemType Directory -Force
238251
}
239-
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'NuGet.exe') -Destination "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet" -Force
240-
if (-not (Test-Path -Path "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208")) {
241-
$null = New-Item -Path "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208" -ItemType Directory -Force
252+
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'NuGet.exe') -Destination $getRoot -Force
253+
254+
$nugetRoot = "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208"
255+
if ($AsCurrentUser) { $nugetRoot = "$env:LOCALAPPDATA\PackageManagement\ProviderAssemblies\nuget\2.8.5.208"}
256+
if (-not (Test-Path -Path $nugetRoot)) {
257+
$null = New-Item -Path $nugetRoot -ItemType Directory -Force
242258
}
243-
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'Microsoft.PackageManagement.NuGetProvider.dll') -Destination "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208" -Force
259+
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'Microsoft.PackageManagement.NuGetProvider.dll') -Destination $nugetRoot -Force
244260
}
245261
else {
246262
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'NuGet.exe') -Destination "$HOME/.config/powershell/powershellget" -Force
@@ -251,6 +267,10 @@
251267
#region V2 Latest
252268
V2Latest {
253269
$modulesFolder = "$env:ProgramFiles\WindowsPowerShell\modules"
270+
if ($AsCurrentUser) {
271+
$modulesFolder = $env:PSModulePath -split ';' | Microsoft.PowerShell.Core\Where-Object { $_ -match '\\Documents\\' } | Microsoft.PowerShell.Utility\Select-Object -First 1
272+
if (-not $modulesFolder) { $env:PSModulePath -split ';' | Microsoft.PowerShell.Utility\Select-Object -First 1 }
273+
}
254274
if (-not $isOnWindows) { $modulesFolder = "/usr/local/share/powershell/Modules" }
255275

256276
Install-ZipModule -Config $data.Config.PSGetV2 -ModulesFolder $modulesFolder -TempFolder $tempFolder
@@ -261,6 +281,10 @@
261281
#region V3 Latest
262282
V3Latest {
263283
$modulesFolder = "$env:ProgramFiles\WindowsPowerShell\modules"
284+
if ($AsCurrentUser) {
285+
$modulesFolder = $env:PSModulePath -split ';' | Microsoft.PowerShell.Core\Where-Object { $_ -match '\\Documents\\' } | Microsoft.PowerShell.Utility\Select-Object -First 1
286+
if (-not $modulesFolder) { $env:PSModulePath -split ';' | Microsoft.PowerShell.Utility\Select-Object -First 1 }
287+
}
264288
if (-not $isOnWindows) { $modulesFolder = "/usr/local/share/powershell/Modules" }
265289

266290
Install-ZipModule -Config $data.Config.PSGetV3 -ModulesFolder $modulesFolder -TempFolder $tempFolder
@@ -285,6 +309,14 @@
285309
$useInternal = $false
286310
}
287311
}
312+
313+
$asCurrentUser = $UserMode.ToBool()
314+
if (-not $asCurrentUser -and
315+
($env:COMPUTERNAME -eq $ComputerName) -and
316+
(-not (Test-PSFPowerShell -Elevated))
317+
) {
318+
$asCurrentUser = $true
319+
}
288320
#endregion Resolve Source Configuration
289321
}
290322
process {
@@ -298,7 +330,7 @@
298330
$binaries = Resolve-PowerShellGet -Type $typeEntry -Offline:$stayOffline -SourcePath $SourcePath -NotInternal:$useInternal
299331

300332
# Execute Deployment
301-
Invoke-PSFCommand -ComputerName $ComputerName -ScriptBlock $code -Credential $Credential -ArgumentList $binaries
333+
Invoke-PSFCommand -ComputerName $ComputerName -ScriptBlock $code -Credential $Credential -ArgumentList $binaries, $asCurrentUser
302334
}
303335
}
304336
end {

PSFramework.NuGet/functions/Resource/Publish-PSFResourceModule.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@
165165
try {
166166
New-DummyModule -Path $stagingDirectory -Name $Name -Version $Version -RequiredModules $RequiredModules -Description $Description -Author $Author
167167
$resources = New-Item -Path $stagingDirectory -Name Resources -ItemType Directory -Force
168-
$Path | Copy-Item -Destination $resources.FullName -Recurse -Force -Confirm:$false -WhatIf:$false
168+
Copy-Item -LiteralPath $($Path) -Destination $resources.FullName -Recurse -Force -Confirm:$false -WhatIf:$false
169+
ConvertTo-TransportFile -Path $resources.FullName
169170

170171
Publish-PSFModule @publishParam -Path $stagingDirectory -ErrorAction Stop
171172
}

PSFramework.NuGet/functions/Resource/Save-PSFResourceModule.ps1

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@
8888
[string[]]
8989
$Name,
9090

91+
[Parameter(Mandatory = $true, Position = 1)]
92+
[PSFDirectory]
93+
$Path,
94+
95+
[PsfArgumentCompleter('PSFramework.NuGet.Repository')]
96+
[string[]]
97+
$Repository = ((Get-PSFrepository).Name | Sort-Object -Unique),
98+
9199
[Parameter(ParameterSetName = 'ByName')]
92100
[string]
93101
$Version,
@@ -96,10 +104,6 @@
96104
[switch]
97105
$Prerelease,
98106

99-
[Parameter(Mandatory = $true, Position = 1)]
100-
[PSFDirectory]
101-
$Path,
102-
103107
[switch]
104108
$SkipDependency,
105109

@@ -112,10 +116,6 @@
112116
[PSCredential]
113117
$Credential,
114118

115-
[PsfArgumentCompleter('PSFramework.NuGet.Repository')]
116-
[string[]]
117-
$Repository = ((Get-PSFrepository).Name | Sort-Object -Unique),
118-
119119
[switch]
120120
$TrustRepository,
121121

@@ -152,6 +152,8 @@
152152
continue
153153
}
154154

155+
ConvertFrom-TransportFile -Path $dataPath
156+
155157
foreach ($item in Get-ChildItem -LiteralPath $dataPath) {
156158
$targetPath = Join-Path -Path $pathEntry -ChildPath $item.Name
157159
if (-not $Force -and (Test-path -Path $targetPath)) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
function ConvertFrom-TransportFile {
2+
<#
3+
.SYNOPSIS
4+
Unwraps a previously created transport file.
5+
6+
.DESCRIPTION
7+
Unwraps a previously created transport file.
8+
These are created as part of the publishing step of resource modules, in order to ensure transport fidelity with PSResourceGet.
9+
This command will expand the transport archive and remove the placeholder files previously created.
10+
11+
.PARAMETER Path
12+
The path to the Resources folder within the Resource Module being downloaded.
13+
14+
.EXAMPLE
15+
PS C:\> ConvertFrom-TransportFile -Path $dataPath
16+
17+
Unwraps any transport file in the specified resources directory.
18+
#>
19+
[CmdletBinding()]
20+
param (
21+
[Parameter(Mandatory = $true)]
22+
[string]
23+
$Path
24+
)
25+
process {
26+
$archivePath = Join-Path -Path $Path -ChildPath '___þþþ_transportplaceholder_þþþ___.zip'
27+
if (-not (Test-Path -LiteralPath $archivePath)) { return }
28+
29+
Expand-Archive -Path $archivePath -DestinationPath $Path
30+
Remove-Item -LiteralPath $archivePath -Force
31+
32+
Get-ChildItem -LiteralPath $Path -Recurse -Force | Where-Object Name -eq '___þþþ_transportplaceholder_þþþ___.txt' | Remove-Item
33+
}
34+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
function ConvertTo-TransportFile {
2+
<#
3+
.SYNOPSIS
4+
Wraps up the payload of a resoure module into a single archive.
5+
6+
.DESCRIPTION
7+
Wraps up the payload of a resoure module into a single archive.
8+
This is unfortunately required to maintain content fidelity, due to errors in the PSResourceGet module.
9+
Before creating the archive, we place a dummy file in every empty folder, to prevent it from being skipped.
10+
11+
.PARAMETER Path
12+
Path to the Resource folder, containing the files & folders to wrap up.
13+
14+
.EXAMPLE
15+
PS C:\> ConvertTo-TransportFile -Path $resourcePath
16+
17+
Wraps up the specified payload into a single archive.
18+
#>
19+
[CmdletBinding()]
20+
param (
21+
[Parameter(Mandatory = $true)]
22+
[string]
23+
$Path
24+
)
25+
process {
26+
$directories = Get-ChildItem -LiteralPath $Path -Recurse -Directory
27+
foreach ($directory in $directories) {
28+
$countChildren = $directory.GetFileSystemInfos('*', [System.IO.SearchOption]::TopDirectoryOnly).Count
29+
if ($countChildren -gt 0) { continue }
30+
31+
$null = New-Item -Path $directory.FullName -Name '___þþþ_transportplaceholder_þþþ___.txt' -ItemType File -Value 42
32+
}
33+
34+
$archivePath = Join-Path -Path $Path -ChildPath '___þþþ_transportplaceholder_þþþ___.zip'
35+
$items = Get-ChildItem -LiteralPath $Path
36+
Compress-Archive -LiteralPath $items.FullName -DestinationPath $archivePath -Force
37+
$items | Remove-Item -Recurse -Force
38+
}
39+
}

PSFramework.NuGet/internal/functions/Get/Save-StagingModule.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@
116116
$tempDirectory = New-PSFTempDirectory -Name StagingSub -ModuleName PSFramework.NuGet
117117
$param = $Item.v2Param
118118
$actualParam = $param + $callSpecifics | ConvertTo-PSFHashtable -ReferenceCommand Save-Module
119+
if ($param.AllowPrerelease -and $actualParam.Keys -notcontains 'AllowPrerelease') {
120+
Write-PSFMessage -Level Warning -String 'Save-PSFModule.AllowPrerease.NotSupported' -Once 'OldPSGetV2_Prerelease'
121+
}
119122

120123
# 1) Save to temp folder
121124
try { Save-Module @actualParam -Path $tempDirectory }

0 commit comments

Comments
 (0)