Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion PSFramework.NuGet/PSFramework.NuGet.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
RootModule = 'PSFramework.NuGet.psm1'

# Version number of this module.
ModuleVersion = '0.9.12'
ModuleVersion = '0.9.16'

# ID used to uniquely identify this module
GUID = 'ad0f2a25-552f-4dd6-bd8e-5ddced2a5d88'
Expand Down
7 changes: 7 additions & 0 deletions PSFramework.NuGet/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.9.16 (2025-05-17)

+ Upd: Install-PSFPowerShellGet - now allows bootstrapping localhost without requiring elevation.
+ Fix: Publish-PSFResourceModule - does not include files with brackets (`[]`) in their name.
+ Fix: Save-PSFResourceModule - does not include empty folders or files when using V3 repositories.
+ Fix: Find-PSFModule - fails (with error) when searching for prerelease versions on a default Windows PowerShell console without any modifications.

## 0.9.12 (2025-05-06)

+ Fix: Install-PSFModule - fails to install on a default Windows PowerShell console without any modifications.
Expand Down
3 changes: 3 additions & 0 deletions PSFramework.NuGet/en-us/strings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
'Copy-Module.Error.StagingFolderCopy' = 'Failed to deploy module to staging directory when trying to publish {0}' # $Path
'Copy-Module.Error.StagingFolderFailed' = 'Failed to create staging folder when trying to publish {0}' # $Path

'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.' #

'Install-PSFModule.Error.Installation' = 'Failed to install {0}' # $Name -join ','
'Install-PSFModule.Error.NoComputerValid' = 'Unable to establish ANY remote connections to {0}' # $ComputerName -join ',
'Install-PSFModule.Error.Setup' = 'Failed to prepare to install {0}' # $Name -join ','
Expand Down Expand Up @@ -65,6 +67,7 @@

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

'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.' #
'Save-PSFModule.Error.NoComputerValid' = 'Failed to connect to any of the provided computer targets: {0}' # ($ComputerName -join ', ')

'Save-PSFResourceModule.Deploying' = 'Deploying {2} from resource module {0} ({1}) to {3}' # $module.Name, $versionFolder.Name, $item.Name, $pathEntry
Expand Down
48 changes: 26 additions & 22 deletions PSFramework.NuGet/functions/Get/Find-PSFModule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -131,52 +131,56 @@
$param = $PSBoundParameters | ConvertTo-PSFHashtable -Include Name, Repository, Tag, Credential, IncludeDependencies
}
process {
#region V2
if ($script:psget.V2 -and $Type -in 'All', 'V2') {
#region V3
if ($script:psget.V3 -and $Type -in 'All', 'V3') {
$paramClone = $param.Clone()
$paramClone += $PSBoundParameters | ConvertTo-PSFHashtable -Include AllVersions, AllowPrerelease
if ($Version) {
if ($convertedVersion.Required) { $paramClone.RequiredVersion = $convertedVersion.Required }
if ($convertedVersion.Minimum) { $paramClone.MinimumVersion = $convertedVersion.Minimum }
if ($convertedVersion.Maximum) { $paramClone.MaximumVersion = $convertedVersion.Maximum }
$paramClone += $PSBoundParameters | ConvertTo-PSFHashtable -Include AllowPrerelease -Remap @{
AllowPrerelease = 'Prerelease'
}
if ($useVersionFilter) {
$paramClone.Version = $versionFilter
}
$paramClone.Type = 'Module'
$execute = $true
if ($paramClone.Repository) {
$paramClone.Repository = $paramClone.Repository | Where-Object {
$_ -match '\*' -or
$_ -in (Get-PSFRepository -Type V2).Name
$_ -in (Get-PSFRepository -Type V3).Name
}
$execute = $paramClone.Repository -as [bool]
}

if ($execute) {
Find-Module @paramClone | ConvertFrom-ModuleInfo
Find-PSResource @paramClone | ConvertFrom-ModuleInfo
}
}
#endregion V2
#endregion V3

#region V3
if ($script:psget.V3 -and $Type -in 'All', 'V3') {
#region V2
if ($script:psget.V2 -and $Type -in 'All', 'V2') {
$paramClone = $param.Clone()
$paramClone += $PSBoundParameters | ConvertTo-PSFHashtable -Include AllowPrerelease -Remap @{
AllowPrerelease = 'Prerelease'
}
if ($useVersionFilter) {
$paramClone.Version = $versionFilter
$paramClone += $PSBoundParameters | ConvertTo-PSFHashtable -Include AllVersions, AllowPrerelease
if ($Version) {
if ($convertedVersion.Required) { $paramClone.RequiredVersion = $convertedVersion.Required }
if ($convertedVersion.Minimum) { $paramClone.MinimumVersion = $convertedVersion.Minimum }
if ($convertedVersion.Maximum) { $paramClone.MaximumVersion = $convertedVersion.Maximum }
}
$paramClone.Type = 'Module'
$execute = $true
if ($paramClone.Repository) {
$paramClone.Repository = $paramClone.Repository | Where-Object {
$_ -match '\*' -or
$_ -in (Get-PSFRepository -Type V3).Name
$_ -in (Get-PSFRepository -Type V2).Name
}
$execute = $paramClone.Repository -as [bool]
}

if ($execute) {
Find-PSResource @paramClone | ConvertFrom-ModuleInfo
$paramClone = $paramClone | ConvertTo-PSFHashtable -ReferenceCommand Find-Module
if ($AllowPrerelease -and $paramClone.Keys -notcontains 'AllowPrerelease') {
Write-PSFMessage -Level Warning -String 'Find-PSFModule.AllowPrerease.NotSupported' -Once 'OldPSGetV2_Prerelease'
}
Find-Module @paramClone | ConvertFrom-ModuleInfo
}
}
#endregion V3
#endregion V2
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
.PARAMETER NotInternal
Do not use the internally provided PowerShellGet module versions.
This REQUIRES you to either provide the module data via -SourcePath or to have live online access.

.PARAMETER UserMode
Deploy the resource into user paths, rather than computer-wide.
This allows bootstrapping _without_ requiring elevation and is usually only needed on the local computer.
This mode is automatically selected when deploying to the local computer and not running PowerShell "As Administrator".
Only applies to / affects Windows computers.

.EXAMPLE
PS C:\> Install-PSFPowerShell -Type V3Latest -ComputerName (Get-ADComputer -Filter * -SearchBase $myOU)
Expand All @@ -69,7 +75,10 @@
$Offline,

[switch]
$NotInternal
$NotInternal,

[switch]
$UserMode
)

begin {
Expand Down Expand Up @@ -161,7 +170,7 @@

$actualConfiguration = Import-PSFPowerShellDataFile -Path (Join-Path -Path $rootPath -ChildPath 'modules.json')
$data = @{
Type = $Type
Type = $Type
Config = $actualConfiguration
}
switch ($Type) {
Expand All @@ -180,7 +189,9 @@
#region Actual Code
$code = {
param (
$Data
$Data,

$AsCurrentUser
)

#region Functions
Expand Down Expand Up @@ -233,14 +244,19 @@
#region V2 Bootstrap
V2Binaries {
if ($isOnWindows) {
if (-not (Test-Path -Path "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet")) {
$null = New-Item -Path "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet" -ItemType Directory -Force
$getRoot = "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet"
if ($AsCurrentUser) { $getRoot = "$env:LocalAppData\Microsoft\Windows\PowerShell\PowerShellGet" }
if (-not (Test-Path -Path $getRoot)) {
$null = New-Item -Path $getRoot -ItemType Directory -Force
}
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'NuGet.exe') -Destination "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet" -Force
if (-not (Test-Path -Path "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208")) {
$null = New-Item -Path "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208" -ItemType Directory -Force
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'NuGet.exe') -Destination $getRoot -Force

$nugetRoot = "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208"
if ($AsCurrentUser) { $nugetRoot = "$env:LOCALAPPDATA\PackageManagement\ProviderAssemblies\nuget\2.8.5.208"}
if (-not (Test-Path -Path $nugetRoot)) {
$null = New-Item -Path $nugetRoot -ItemType Directory -Force
}
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'Microsoft.PackageManagement.NuGetProvider.dll') -Destination "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208" -Force
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'Microsoft.PackageManagement.NuGetProvider.dll') -Destination $nugetRoot -Force
}
else {
Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'NuGet.exe') -Destination "$HOME/.config/powershell/powershellget" -Force
Expand All @@ -251,6 +267,10 @@
#region V2 Latest
V2Latest {
$modulesFolder = "$env:ProgramFiles\WindowsPowerShell\modules"
if ($AsCurrentUser) {
$modulesFolder = $env:PSModulePath -split ';' | Microsoft.PowerShell.Core\Where-Object { $_ -match '\\Documents\\' } | Microsoft.PowerShell.Utility\Select-Object -First 1
if (-not $modulesFolder) { $env:PSModulePath -split ';' | Microsoft.PowerShell.Utility\Select-Object -First 1 }
}
if (-not $isOnWindows) { $modulesFolder = "/usr/local/share/powershell/Modules" }

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

Install-ZipModule -Config $data.Config.PSGetV3 -ModulesFolder $modulesFolder -TempFolder $tempFolder
Expand All @@ -285,6 +309,14 @@
$useInternal = $false
}
}

$asCurrentUser = $UserMode.ToBool()
if (-not $asCurrentUser -and
($env:COMPUTERNAME -eq $ComputerName) -and
(-not (Test-PSFPowerShell -Elevated))
) {
$asCurrentUser = $true
}
#endregion Resolve Source Configuration
}
process {
Expand All @@ -298,7 +330,7 @@
$binaries = Resolve-PowerShellGet -Type $typeEntry -Offline:$stayOffline -SourcePath $SourcePath -NotInternal:$useInternal

# Execute Deployment
Invoke-PSFCommand -ComputerName $ComputerName -ScriptBlock $code -Credential $Credential -ArgumentList $binaries
Invoke-PSFCommand -ComputerName $ComputerName -ScriptBlock $code -Credential $Credential -ArgumentList $binaries, $asCurrentUser
}
}
end {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@
try {
New-DummyModule -Path $stagingDirectory -Name $Name -Version $Version -RequiredModules $RequiredModules -Description $Description -Author $Author
$resources = New-Item -Path $stagingDirectory -Name Resources -ItemType Directory -Force
$Path | Copy-Item -Destination $resources.FullName -Recurse -Force -Confirm:$false -WhatIf:$false
Copy-Item -LiteralPath $($Path) -Destination $resources.FullName -Recurse -Force -Confirm:$false -WhatIf:$false
ConvertTo-TransportFile -Path $resources.FullName

Publish-PSFModule @publishParam -Path $stagingDirectory -ErrorAction Stop
}
Expand Down
18 changes: 10 additions & 8 deletions PSFramework.NuGet/functions/Resource/Save-PSFResourceModule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@
[string[]]
$Name,

[Parameter(Mandatory = $true, Position = 1)]
[PSFDirectory]
$Path,

[PsfArgumentCompleter('PSFramework.NuGet.Repository')]
[string[]]
$Repository = ((Get-PSFrepository).Name | Sort-Object -Unique),

[Parameter(ParameterSetName = 'ByName')]
[string]
$Version,
Expand All @@ -96,10 +104,6 @@
[switch]
$Prerelease,

[Parameter(Mandatory = $true, Position = 1)]
[PSFDirectory]
$Path,

[switch]
$SkipDependency,

Expand All @@ -112,10 +116,6 @@
[PSCredential]
$Credential,

[PsfArgumentCompleter('PSFramework.NuGet.Repository')]
[string[]]
$Repository = ((Get-PSFrepository).Name | Sort-Object -Unique),

[switch]
$TrustRepository,

Expand Down Expand Up @@ -152,6 +152,8 @@
continue
}

ConvertFrom-TransportFile -Path $dataPath

foreach ($item in Get-ChildItem -LiteralPath $dataPath) {
$targetPath = Join-Path -Path $pathEntry -ChildPath $item.Name
if (-not $Force -and (Test-path -Path $targetPath)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
function ConvertFrom-TransportFile {
<#
.SYNOPSIS
Unwraps a previously created transport file.

.DESCRIPTION
Unwraps a previously created transport file.
These are created as part of the publishing step of resource modules, in order to ensure transport fidelity with PSResourceGet.
This command will expand the transport archive and remove the placeholder files previously created.

.PARAMETER Path
The path to the Resources folder within the Resource Module being downloaded.

.EXAMPLE
PS C:\> ConvertFrom-TransportFile -Path $dataPath

Unwraps any transport file in the specified resources directory.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$Path
)
process {
$archivePath = Join-Path -Path $Path -ChildPath '___þþþ_transportplaceholder_þþþ___.zip'
if (-not (Test-Path -LiteralPath $archivePath)) { return }

Expand-Archive -Path $archivePath -DestinationPath $Path
Remove-Item -LiteralPath $archivePath -Force

Get-ChildItem -LiteralPath $Path -Recurse -Force | Where-Object Name -eq '___þþþ_transportplaceholder_þþþ___.txt' | Remove-Item
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
function ConvertTo-TransportFile {
<#
.SYNOPSIS
Wraps up the payload of a resoure module into a single archive.

.DESCRIPTION
Wraps up the payload of a resoure module into a single archive.
This is unfortunately required to maintain content fidelity, due to errors in the PSResourceGet module.
Before creating the archive, we place a dummy file in every empty folder, to prevent it from being skipped.

.PARAMETER Path
Path to the Resource folder, containing the files & folders to wrap up.

.EXAMPLE
PS C:\> ConvertTo-TransportFile -Path $resourcePath

Wraps up the specified payload into a single archive.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$Path
)
process {
$directories = Get-ChildItem -LiteralPath $Path -Recurse -Directory
foreach ($directory in $directories) {
$countChildren = $directory.GetFileSystemInfos('*', [System.IO.SearchOption]::TopDirectoryOnly).Count
if ($countChildren -gt 0) { continue }

$null = New-Item -Path $directory.FullName -Name '___þþþ_transportplaceholder_þþþ___.txt' -ItemType File -Value 42
}

$archivePath = Join-Path -Path $Path -ChildPath '___þþþ_transportplaceholder_þþþ___.zip'
$items = Get-ChildItem -LiteralPath $Path
Compress-Archive -LiteralPath $items.FullName -DestinationPath $archivePath -Force
$items | Remove-Item -Recurse -Force
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@
$tempDirectory = New-PSFTempDirectory -Name StagingSub -ModuleName PSFramework.NuGet
$param = $Item.v2Param
$actualParam = $param + $callSpecifics | ConvertTo-PSFHashtable -ReferenceCommand Save-Module
if ($param.AllowPrerelease -and $actualParam.Keys -notcontains 'AllowPrerelease') {
Write-PSFMessage -Level Warning -String 'Save-PSFModule.AllowPrerease.NotSupported' -Once 'OldPSGetV2_Prerelease'
}

# 1) Save to temp folder
try { Save-Module @actualParam -Path $tempDirectory }
Expand Down
Loading