diff --git a/PSFramework.NuGet/PSFramework.NuGet.psd1 b/PSFramework.NuGet/PSFramework.NuGet.psd1
index 2d2406f..6200ab5 100644
--- a/PSFramework.NuGet/PSFramework.NuGet.psd1
+++ b/PSFramework.NuGet/PSFramework.NuGet.psd1
@@ -12,21 +12,21 @@
 	Author            = 'Friedrich Weinmann'
 	
 	# Company or vendor of this module
-	CompanyName       = 'Microsoft'
+	CompanyName       = ' '
 	
 	# Copyright statement for this module
-	Copyright         = 'Copyright (c) 2023 Friedrich Weinmann'
+	Copyright         = 'Copyright (c) 2024 Friedrich Weinmann'
 	
 	# Description of the functionality provided by this module
 	Description       = 'A wrapper around the PowerShellGet modules'
 	
 	# Minimum version of the Windows PowerShell engine required by this module
-	PowerShellVersion = '5.0'
+	PowerShellVersion = '5.1'
 	
 	# Modules that must be imported into the global environment prior to importing
 	# this module
 	RequiredModules   = @(
-		@{ ModuleName = 'PSFramework'; ModuleVersion = '1.7.270' }
+		@{ ModuleName = 'PSFramework'; ModuleVersion = '1.12.346' }
 	)
 	
 	# Assemblies that must be loaded prior to importing this module
@@ -41,6 +41,7 @@
 	# Functions to export from this module
 	FunctionsToExport = @(
 		'Find-PSFModule'
+		'Get-PSFModuleSignature'
 		'Get-PSFPowerShellGet'
 		'Get-PSFRepository'
 		'Install-PSFModule'
@@ -77,13 +78,13 @@
 		PSData = @{
 			
 			# Tags applied to this module. These help with module discovery in online galleries.
-			# Tags = @()
+			Tags = @('nuget', 'modules', 'psresource')
 			
 			# A URL to the license for this module.
-			# LicenseUri = ''
+			LicenseUri = 'https://github.com/PowershellFrameworkCollective/PSFramework.NuGet/blob/master/LICENSE'
 			
 			# A URL to the main website for this project.
-			# ProjectUri = ''
+			ProjectUri = 'https://github.com/PowershellFrameworkCollective/PSFramework.NuGet'
 			
 			# A URL to an icon representing this module.
 			# IconUri = ''
diff --git a/PSFramework.NuGet/functions/Get/Get-PSFRepository.ps1 b/PSFramework.NuGet/functions/Get/Get-PSFRepository.ps1
index bce5b5d..d6d621c 100644
--- a/PSFramework.NuGet/functions/Get/Get-PSFRepository.ps1
+++ b/PSFramework.NuGet/functions/Get/Get-PSFRepository.ps1
@@ -39,7 +39,7 @@
 					Type       = 'V2'
 					Status     = $status
 					Trusted    = $repository.Trusted
-					Priority   = -1
+					Priority   = Get-PSFConfigValue -FullName "PSFramework.NuGet.Repositories.$($repository.Name).Priority" -Fallback 100
 					Uri        = $repository.SourceLocation
 					Object     = $repository
 				}
diff --git a/PSFramework.NuGet/functions/Get/Save-PSFModule.ps1 b/PSFramework.NuGet/functions/Get/Save-PSFModule.ps1
index 0edc18f..f38faf4 100644
--- a/PSFramework.NuGet/functions/Get/Save-PSFModule.ps1
+++ b/PSFramework.NuGet/functions/Get/Save-PSFModule.ps1
@@ -1,20 +1,75 @@
-function Save-PSFModule
-{
-	[CmdletBinding()]
+function Save-PSFModule {
+	[CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = 'ByName', SupportsShouldProcess = $true)]
 	Param (
-	
+		[Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ByName')]
+		[string[]]
+		$Name,
+
+		[Parameter(ParameterSetName = 'ByName')]
+		[string]
+		$Version,
+
+		[Parameter(ParameterSetName = 'ByName')]
+		[switch]
+		$Prerelease,
+
+		[Parameter(Mandatory = $true, Position = 1)]
+		[string]
+		$Path,
+
+		[PSFComputer[]]
+		$ComputerName,
+
+		[switch]
+		$SkipDependency,
+
+		[switch]
+		$AuthenticodeCheck = (Get-PSFConfigValue -FullName 'PSFramework.NuGet.Install.AuthenticodeSignature.Check'),
+
+		[switch]
+		$Force,
+
+		[PSCredential]
+		$Credential,
+
+		[PsfArgumentCompleter('PSFramework.NuGet.Repository')]
+		[string[]]
+		$Repository,
+
+		[switch]
+		$TrustRepository,
+
+		[ValidateSet('All', 'V2', 'V3')]
+		[string]
+		$Type = 'All',
+
+		[Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ByObject')]
+		[object[]]
+		$InputObject
 	)
 	
-	begin
-	{
+	begin {
+		$repositories = Resolve-Repository -Name $Repository -Type $Type -Cmdlet $PSCmdlet # Terminates if no repositories found
+		$resolvedPaths = Resolve-RemotePath -Path $Path -ComputerName $ComputerName -TargetHandling Any -Cmdlet $PSCmdlet # Errors for bad paths, terminates if no path
 		
+		$tempDirectory = New-PSFTempDirectory -Name Staging -ModuleName PSFramework.NuGet
 	}
-	process
-	{
-		#TODO: Implement
-	}
-	end
-	{
-	
+	process {
+		try {
+			$installData = switch ($PSCmdlet.ParameterSetName) {
+				ByObject { Resolve-ModuleTarget -InputObject $InputObject -Cmdlet $PSCmdlet }
+				ByName { Resolve-ModuleTarget -Name $Name -Version $Version -Prerelease:$Prerelease -Cmdlet $PSCmdlet }
+			}
+			if (-not $PSCmdlet.ShouldProcess(($installData.Name -join ', '), "Saving modules to $Path")) {
+				return
+			}
+			
+			Save-StagingModule -InstallData $installData -Path $tempDirectory -Repositories $repositories -Cmdlet $PSCmdlet -Credential $Credential -SkipDependency:$SkipDependency -AuthenticodeCheck:$AuthenticodeCheck
+			#TODO: Implement
+			Publish-StagingModule -Path $tempDirectory -TargetPath $resolvedPaths -Force:$Force
+		}
+		finally {
+			Remove-PSFTempItem -Name Staging -ModuleName PSFramework.NuGet
+		}
 	}
 }
diff --git a/PSFramework.NuGet/functions/Get/Update-PSFRepository.ps1 b/PSFramework.NuGet/functions/Get/Update-PSFRepository.ps1
index f613846..133cda4 100644
--- a/PSFramework.NuGet/functions/Get/Update-PSFRepository.ps1
+++ b/PSFramework.NuGet/functions/Get/Update-PSFRepository.ps1
@@ -11,7 +11,7 @@
 
 		Available settings:
 		- Uri: Url or filesystem path to the repository. Used for both install and publish.
-		- Priority: Priority of a PowerShellGet V3 Repository. Numeric value, determines repository precedence
+		- Priority: Priority of a PowerShell Repository. Numeric value, determines repository precedence.
 		- Type: What kind of PowerShellGet version to apply the configuration to. Details on the options below. Defaults to 'Any'.
 		- Trusted: Whether the repository should be trusted. Can be set to 0, 1, $false or $true. Defaults to $true.
 		- Present: Whether the repository should exist at all. Can be set to 0, 1, $false or $true. Defaults to $true.
diff --git a/PSFramework.NuGet/functions/PowerShellGet/Get-PSFPowerShellGet.ps1 b/PSFramework.NuGet/functions/PowerShellGet/Get-PSFPowerShellGet.ps1
index 54cdf07..f7b8620 100644
--- a/PSFramework.NuGet/functions/PowerShellGet/Get-PSFPowerShellGet.ps1
+++ b/PSFramework.NuGet/functions/PowerShellGet/Get-PSFPowerShellGet.ps1
@@ -34,6 +34,7 @@
 	begin {
 		$code = {
 			$modules = Get-Module -Name PowerShellGet -ListAvailable
+			$modulesV3 = Get-Module -Name Microsoft.PowerShell.PSResourceGet -ListAvailable
 
 			$isOnWindows = $PSVersionTable.PSVersion.Major -lt 6 -or $isWindows
 			if ($isOnWindows) {
@@ -52,7 +53,7 @@
 				PSTypeName = 'PSFramework.NuGet.GetReport'
 				ComputerName = $env:COMPUTERNAME
 				V2           = ($modules | Where-Object { $_.Version.Major -lt 3 }) -as [bool]
-				V3           = ($modules | Where-Object { $_.Version.Major -eq 3 }) -as [bool]
+				V3           = $modulesV3 -as [bool]
 				V2CanInstall = $v2CanInstall
 				V2CanPublish = Test-Path -Path $nugetPath
 				Modules      = $modules
diff --git a/PSFramework.NuGet/functions/PowerShellGet/Install-PSFPowerShellGet.ps1 b/PSFramework.NuGet/functions/PowerShellGet/Install-PSFPowerShellGet.ps1
index 40542ce..25ccaea 100644
--- a/PSFramework.NuGet/functions/PowerShellGet/Install-PSFPowerShellGet.ps1
+++ b/PSFramework.NuGet/functions/PowerShellGet/Install-PSFPowerShellGet.ps1
@@ -80,7 +80,7 @@
 						Resolved = $resolvedUrl
 						FileName = ''
 					}
-					$onlineVersion[$link].FileName = '{0}-{1}.zip' -f $pkgData[$link].Name, $pkgData[$link].Version
+					$onlineVersion[$link].FileName = '{0}-{1}.zip' -f $onlineVersion[$link].Name, $onlineVersion[$link].Version
 				}
 			}
 			#endregion Check Online
@@ -99,6 +99,7 @@
 			
 			# If online version is newer than internal, download to appdata as cached version
 			if ('Online' -eq $source) {
+				if (-not (Test-Path -Path $SourcePath)) { $null = New-Item -Path $SourcePath -ItemType Directory -Force }
 				Save-PSFPowerShellGet -Path $SourcePath # This can never happen if the user specified a path, so no risk of overwriting.
 			}
 
@@ -182,7 +183,13 @@
 				#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
+						}
 						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 'Microsoft.PackageManagement.NuGetProvider.dll') -Destination "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208" -Force
 					}
 					else {
diff --git a/PSFramework.NuGet/functions/PowerShellGet/Save-PSFPowerShellGet.ps1 b/PSFramework.NuGet/functions/PowerShellGet/Save-PSFPowerShellGet.ps1
index 89cc214..31e6db9 100644
--- a/PSFramework.NuGet/functions/PowerShellGet/Save-PSFPowerShellGet.ps1
+++ b/PSFramework.NuGet/functions/PowerShellGet/Save-PSFPowerShellGet.ps1
@@ -1,7 +1,25 @@
 function Save-PSFPowerShellGet {
+	<#
+	.SYNOPSIS
+		Downloads and provides the latest packages for both PowerShellGet V2 and V3.
+	
+	.DESCRIPTION
+		Downloads and provides the latest packages for both PowerShellGet V2 and V3.
+		These can then be used by this module to deploy and bootstrap offline computers with package management tooling.
+	
+	.PARAMETER Path
+		The path where to deploy the module packages as zip-files.
+		Must be a directory.
+		Defaults to: %AppData%/PowerShell/PSFramework/modules/PowerShellGet
+	
+	.EXAMPLE
+		PS C:\> Save-PSFPowerShellGet
+		
+		Downloads and deploys the latest version of Get V2 & V3 to "%AppData%/PowerShell/PSFramework/modules/PowerShellGet"
+	#>
 	[CmdletBinding()]
 	param (
-		[PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorMessage = 'PSFramework.Validate.FSPath.Folder')]
+		[PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')]
 		[string]
 		$Path
 	)
diff --git a/PSFramework.NuGet/functions/Signing/Get-PSFModuleSignature.ps1 b/PSFramework.NuGet/functions/Signing/Get-PSFModuleSignature.ps1
new file mode 100644
index 0000000..1ffc3ad
--- /dev/null
+++ b/PSFramework.NuGet/functions/Signing/Get-PSFModuleSignature.ps1
@@ -0,0 +1,200 @@
+function Get-PSFModuleSignature {
+	<#
+	.SYNOPSIS
+		Verifies, whether a module is properly signed.
+	
+	.DESCRIPTION
+		Verifies, whether a module is properly signed.
+		Iterates over every module file and verifies its signature.
+
+		The result reports:
+		- Overall signing status
+		- Signatures not Timestamped count
+		- Status Summary
+		- Subject of signing certs summary
+		- Issuer of signing certs summary
+
+		A module should be considered signed, when ...
+		- the over signing status is valid
+		- the subjects are expected (A microsoft module being signed by a microsoft code signing cert, etc.)
+		- the issuer CAs are expected (A microsoft module being signed by a cert issued by Microsoft, etc.)
+	
+	.PARAMETER Path
+		Path to the module(s) to scan.
+		Should be the path to either a module-root or a psd1 file.
+	
+	.EXAMPLE
+		PS C:\> Get-PSFModuleSignature -Path .
+		
+		Returns, whether the module in the current path is signed.
+
+	.EXAMPLE
+		PS C:\> Get-PSFModuleSignature -Path \\contoso.com\it\coding\modules\ContosoTools
+
+		Verifies the code signing of the module stored in \\contoso.com\it\coding\modules\ContosoTools
+
+	.EXAMPLE
+		PS C:\> Get-Module | Get-PSFModuleSignature
+
+		Verifies for each currently loaded module, whether they are signed.
+	#>
+	[CmdletBinding()]
+	param (
+		[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
+		[Alias('ModuleBase', 'FullName')]
+		[string[]]
+		$Path
+	)
+	begin {
+		function Resolve-ModulePath {
+			[CmdletBinding()]
+			param (
+				[string[]]
+				$Path,
+
+				$Cmdlet
+			)
+
+			foreach ($pathItem in $Path) {
+				try { $resolvedPaths = Resolve-Path $pathItem }
+				catch {
+					$record = [System.Management.Automation.ErrorRecord]::new(
+						[Exception]::new("Path not found: $pathItem", $_.Exception),
+						"InvalidPath",
+						[System.Management.Automation.ErrorCategory]::InvalidArgument,
+						$pathItem
+					)
+					$Cmdlet.WriteError($record)
+					continue
+				}
+
+				foreach ($resolvedPath in $resolvedPaths) {
+					$item = Get-Item -LiteralPath $resolvedPath
+					if ($item.PSIsContainer) {
+						$manifests = Get-ChildItem -LiteralPath $item.FullName -Filter *.psd1 -Recurse -ErrorAction SilentlyContinue
+						if (-not $manifests) {
+							$record = [System.Management.Automation.ErrorRecord]::new(
+								[Exception]::new("No module found in: $resolvedPath (resolved from $pathItem)"),
+								"ObjectNotFound",
+								[System.Management.Automation.ErrorCategory]::InvalidArgument,
+								$pathItem
+							)
+							$Cmdlet.WriteError($record)
+							continue
+						}
+
+						foreach ($manifest in $manifests) {
+							$manifest.Directory.FullName
+						}
+						continue
+					}
+
+					if ($item.Extension -in '.psd1', 'psm1') {
+						$item.Directory.FullName
+						continue
+					}
+					if (Get-Item -Path "$($item.Directory.FullName)\*.psd1") {
+						$item.Directory.FullName
+						continue
+					}
+
+					$record = [System.Management.Automation.ErrorRecord]::new(
+						[Exception]::new("Unexpected file: $resolvedPaht from $pathItem"),
+						"UnexpectedPath",
+						[System.Management.Automation.ErrorCategory]::InvalidArgument,
+						$pathItem
+					)
+					$Cmdlet.WriteError($record)
+				}
+			}
+		}
+		
+		function Get-ModuleSignatureInternal {
+			[CmdletBinding()]
+			param (
+				[Parameter(Mandatory = $true)]
+				[string]
+				$Path
+			)
+
+			$signatureStatus = Get-ChildItem -LiteralPath $Path -Recurse -File | ForEach-Object {
+				$currentItem = $_.FullName
+				try { Get-AuthenticodeSignature -LiteralPath $currentItem }
+				catch {
+					[PSCustomObject]@{
+						PSTypeName             = 'System.Management.Automation.Signature'
+						SignerCertificate      = $null
+						TimeStamperCertificate = $null
+						Status                 = 'AccessError'
+						StatusMessage          = $_
+						Path                   = $currentItem
+						SignatureType          = $null
+						IsOSBinary             = $false
+					}
+				}
+			}
+
+			$manifest = Get-ChildItem -LiteralPath $Path -Filter *.psd1 | Select-Object -First 1
+			$manifestData = @{}
+			if ($manifest) {
+				$manifestData = Import-PowerShellDataFile -LiteralPath $manifest.FullName
+			}
+
+			[PSCustomObject]@{
+				ModuleBase       = $Path
+				Name             = $manifest.BaseName
+				Version          = $manifestData.ModuleVersion
+				IsSigned         = -not @($signatureStatus).Where{ $_.Status -notin 'Valid', 'UnknownError' }
+				FileCount        = @($signatureStatus).Count
+				NoTimestampCount = @($signatureStatus).Where{ $_.SignerCertificate -and -not $_.TimeStamperCertificate }.Count
+				ByStatus         = ConvertTo-SigningSummary -Results $signatureStatus -Type Status
+				ByIssuer         = ConvertTo-SigningSummary -Results $signatureStatus -Type Issuer
+				BySubject        = ConvertTo-SigningSummary -Results $signatureStatus -Type Subject
+				Signatures       = $signatureStatus
+			}
+		}
+		function ConvertTo-SigningSummary {
+			[CmdletBinding()]
+			param (
+				[AllowEmptyCollection()]
+				$Results,
+
+				[Parameter(Mandatory = $true)]
+				[ValidateSet('Issuer', 'Subject', 'Status')]
+				[string]
+				$Type
+			)
+
+			$groupBy = @{
+				Issuer  = { $_.SignerCertificate.Issuer }
+				Subject = { $_.SignerCertificate.Subject }
+				Status  = 'Status'
+			}
+
+			$groups = $Results | Group-Object $groupBy[$Type]
+			$hash = @{ }
+			foreach ($group in $groups) {
+				$hash[$group.Name] = $group.Group
+			}
+			$entry = [PSCustomObject]@{
+				TotalCount = @($Results).Count
+				GroupCount = @($groups).Count
+				Results    = $hash
+			}
+			Add-Member -InputObject $entry -MemberType ScriptMethod -Name ToString -Force -Value {
+				$lines = foreach ($pair in $this.Results.GetEnumerator()) {
+					if (-not $pair.Key) { continue }
+					if ($pair.Key -eq 'UnknownError') { '{0}: {1} (Usually: File format that cannot be signed)' -f $pair.Value.Count, $pair.Key }
+					else { '{0}: {1}' -f $pair.Value.Count, $pair.Key }
+				}
+				$lines -join "`n"
+			}
+			$entry
+		}
+	}
+	process {
+		foreach ($inputPath in Resolve-ModulePath -Path $Path -Cmdlet $PSCmdlet | Sort-Object -Unique) {
+			Get-ModuleSignatureInternal -Path $inputPath
+		}
+	}
+}
\ No newline at end of file
diff --git a/PSFramework.NuGet/internal/configurations/configuration.ps1 b/PSFramework.NuGet/internal/configurations/configuration.ps1
index de7800b..cf80dd5 100644
--- a/PSFramework.NuGet/internal/configurations/configuration.ps1
+++ b/PSFramework.NuGet/internal/configurations/configuration.ps1
@@ -12,4 +12,6 @@ Set-PSFConfig -Module 'PSFramework.NuGet' -Name 'Example.Setting' -Value 10 -Ini
 #>
 
 Set-PSFConfig -Module 'PSFramework.NuGet' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging."
-Set-PSFConfig -Module 'PSFramework.NuGet' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."
\ No newline at end of file
+Set-PSFConfig -Module 'PSFramework.NuGet' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."
+
+Set-PSFConfig -Module 'PSFramework.NuGet' -Name 'Install.AuthenticodeSignature.Check' -Value $false -Initialize -Validation 'bool' -Description 'Whether on installation or download of module its code-signing will be checked first.'
\ No newline at end of file
diff --git a/PSFramework.NuGet/internal/functions/Get/Read-VersionString.ps1 b/PSFramework.NuGet/internal/functions/Get/Read-VersionString.ps1
new file mode 100644
index 0000000..88c2b66
--- /dev/null
+++ b/PSFramework.NuGet/internal/functions/Get/Read-VersionString.ps1
@@ -0,0 +1,119 @@
+function Read-VersionString {
+	<#
+	.SYNOPSIS
+		Parses a Version String to work for PSGet V2 & V3
+	
+	.DESCRIPTION
+		Parses a Version String to work for PSGet V2 & V3
+
+		Supported Syntax:
+		<Prefix><Version><Connector><Version><Suffix>
+
+		Prefix: "[" (-ge) or "(" (-gt) or nothing (-ge)
+		Version: A valid version of 2-4 elements or nothing
+		Connector: A "," or a "-"
+		Suffix: "]" (-le) or ")" (-lt) or nothing (-le)
+	
+	.PARAMETER Version
+		The Version string to parse.
+	
+	.PARAMETER Cmdlet
+		The $PSCmdlet variable of the caller.
+		As this is an internal utility command, this allows it to terminate in the context of the calling command and remain invisible to the user.
+	
+	.EXAMPLE
+		PS C:\> Read-VersionString -Version '[1.0.0,2.0.0)' -Cmdlet $PSCmdlet
+		
+		Resolves to a version object with a minimum version of 1.0.0 and less than 2.0.0.
+	#>
+	[CmdletBinding()]
+	param (
+		[Parameter(Mandatory = $true)]
+		[string]
+		$Version,
+
+		$Cmdlet = $PSCmdlet
+	)
+	process {
+		$result = [PSCustomObject]@{
+			V3String = ''
+			Required = ''
+			Minimum = ''
+			Maximum = ''
+			Prerelease = $false
+		}
+
+		# Plain Version
+		if ($Version -as [version]) {
+			$result.V3String = $Version
+			$result.Required = $Version
+			return $result
+		}
+
+		# Plain Version with Prerelease Tag
+		if ($Version -match '^\d+(\.\d+){1,3}-\D') {
+			$result.V3String = $Version -replace '-\D.*$'
+			$result.Required = $Version -replace '-\D.*$'
+			$result.Prerelease = $true
+			return $result
+		}
+
+		<#
+		Must match <Prefix><Version><Connector><Version><Suffix>
+		Prefix: "[" (-ge) or "(" (-gt) or nothing (-ge)
+		Version: A valid version of 2-4 elements or nothing
+		Connector: A "," or a "-"
+		Suffix: "]" (-le) or ")" (-lt) or nothing (-le)
+		#>
+		if ($Version -notmatch '^(\[|\(){0,1}(\d+(\.\d+){1,3}){0,1}(-|,)(\d+(\.\d+){1,3}){0,1}(\]|\)){0,1}$') {
+			Stop-PSFFunction -String 'Read-VersionString.Error.BadFormat' -StringValues $Version -EnableException $true -Cmdlet $Cmdlet -Category InvalidArgument
+		}
+
+		$startGT = $Version -match '^\('
+		$endGT = $Version -match '\)$'
+		$lower, $higher = $Version -replace '\[|\]|\(|\)' -split ',|-'
+
+		$v3Start = '['
+		if ($startGT) { $v3Start = '(' }
+		$v3End = ']'
+		if ($endGT) { $v3End = ')' }
+		$result.V3String = "$($v3Start)$($lower),$($higher)$($v3End)"
+		if ($lower) {
+			$result.Minimum = $lower -as [version]
+			if ($startGT) {
+				$parts = $lower -split '\.'
+				$parts[-1] = 1 + $parts[-1]
+				$result.Minimum = $parts -join '.'
+			}
+		}
+		if ($higher) {
+			if ($higher -match '^0(\.0){1,3}$') {
+				Stop-PSFFunction -String 'Read-VersionString.Error.BadFormat.ZeroUpperBound' -StringValues $Version -EnableException $true -Cmdlet $Cmdlet -Category InvalidArgument
+			}
+
+			$result.Maximum = $higher -as [version]
+			if ($endGT) {
+				$parts = $higher -split '\.'
+				$index = $parts.Count - 1
+				do {
+					if (0 -lt $parts[$index]) {
+						$parts[$index] = -1 + $parts[$index]
+						break
+					}
+					$index--
+				}
+				until ($index -lt 0)
+
+				if ($index -lt ($parts.Count - 1)) {
+					foreach ($position in ($index + 1)..($parts.Count - 1)) {
+						$parts[$position] = 999
+					}
+				}
+
+				$result.Maximum = $parts -join '.'
+			}
+		}
+
+		$result
+	}
+}
\ No newline at end of file
diff --git a/PSFramework.NuGet/internal/functions/Get/Resolve-ModuleTarget.ps1 b/PSFramework.NuGet/internal/functions/Get/Resolve-ModuleTarget.ps1
new file mode 100644
index 0000000..6590518
--- /dev/null
+++ b/PSFramework.NuGet/internal/functions/Get/Resolve-ModuleTarget.ps1
@@ -0,0 +1,154 @@
+function Resolve-ModuleTarget {
+	<#
+	.SYNOPSIS
+		Resolves the search criteria for modules to save or install.
+	
+	.DESCRIPTION
+		Resolves the search criteria for modules to save or install.
+		For each specified module, it will return a result including the parameters Save-Module and Save-PSResource will need.
+	
+	.PARAMETER InputObject
+		A module object to retrieve. Can be the output of Get-Module, Find-Module, Find-PSResource or Find-PSFModule.
+	
+	.PARAMETER Name
+		The name of the module to resolve.
+	
+	.PARAMETER Version
+		The version condition for the module. Supports a fairly flexible syntax.
+		Examples:
+		- 2.0.0 # Exactly v2.0.0
+		- 2.1.0-RC2 # Preview "RC2" of exactly version 2.1.0
+		- 2.0.0-2.4.5 # Any version at least 2.0.0 and at most 2.4.5
+		- [2.0,3.0) # At least 2.0 but less than 3.0
+		- [2.0-3.0) # At least 2.0 but less than 3.0
+	
+	.PARAMETER Prerelease
+		Include Prerelease versions.
+		Redundant if asking for a specific version with a specific prerelease suffix.
+	
+	.PARAMETER Cmdlet
+		The $PSCmdlet variable of the caller.
+		As this is an internal utility command, this allows it to terminate in the context of the calling command and remain invisible to the user.
+	
+	.EXAMPLE
+		PS C:\> Resolve-ModuleTarget -InputObject $InputObject -Cmdlet $PSCmdlet
+
+		Resolves the object as a module target.
+		In case of error, the terminating error will happen within the scope of the caller.
+	#>
+	[CmdletBinding()]
+	param (
+		[Parameter(ValueFromPipeline = $true, ParameterSetName = 'ByObject')]
+		[object[]]
+		$InputObject,
+
+		[Parameter(ParameterSetName = 'ByName')]
+		[string[]]
+		$Name,
+
+		[Parameter(ParameterSetName = 'ByName')]
+		[AllowEmptyString()]
+		[string]
+		$Version,
+
+		[Parameter(ParameterSetName = 'ByName')]
+		[switch]
+		$Prerelease,
+
+		$Cmdlet = $PSCmdlet
+	)
+	begin {
+		function New-ModuleTarget {
+			[CmdletBinding()]
+			param (
+				$Object,
+
+				[string]
+				$Name,
+
+				[AllowEmptyString()]
+				[string]
+				$Version,
+
+				[switch]
+				$Prerelease,
+
+				$Cmdlet
+			)
+
+			$v2Param = @{ }
+			$v3Param = @{ }
+			$actualName = $Name
+			$versionString = ''
+
+			if ($Object) {
+				$v3Param.InputObject = $Object
+				$v2Param.Name = $Object.Name
+				$v2Param.RequiredVersion = $Object.AdditionalMetadata.NormalizedVersion
+				$versionString = $Object.AdditionalMetadata.NormalizedVersion
+				$actualName = $Object.Name
+
+				# V3
+				if ($Object.IsPrerelease) { $v2Param.AllowPrerelease = $true }
+				# V2
+				if ($Object.AdditionalMetadata.IsPrerelease) { $v2Param.AllowPrerelease = $true }
+
+				# Get-Module
+				if ($Object -is [System.Management.Automation.PSModuleInfo]) {
+					$versionString = $Object.Version
+					$v2Param.RequiredVersion = $Object.Version
+					if ($Object.PrivateData.PSData.Prerelease) {
+						$v2Param.AllowPrerelease = $true
+						$v2Param.RequiredVersion = '{0}-{1}' -f $Object.Version, $Object.PrivateData.PSData.Prerelease
+					}
+				}
+			}
+			else {
+				$v2Param.Name = $Name
+				$v3Param.Name = $Name
+				if ($Prerelease) {
+					$v2Param.AllowPrerelease = $true
+					$v3Param.Prerelease = $true
+				}
+				if ($Version) {
+					$versionData = Read-VersionString -Version $Version -Cmdlet $Cmdlet
+					$v3Param.Version = $versionData.V3String
+					if ($versionData.Required) { $v2Param.RequiredVersion = $versionData.Required }
+					else {
+						if ($versionData.Minimum) { $v2Param.MinimumVersion = $versionData.Minimum }
+						if ($versionData.Maximum) { $v2Param.MaximumVersion = $versionData.Maximum }
+					}
+					if ($versionData.Prerelease) {
+						$v2Param.AllowPrerelease = $true
+						$v3Param.Prerelease = $true
+					}
+				}
+			}
+
+			[PSCustomObject]@{
+				PSTypeName = 'PSFramework.NuGet.ModuleTarget'
+				Name       = $actualName
+				Version    = $versionString
+				V2Param    = $v2Param
+				V3Param    = $v3Param
+			}
+		}
+	}
+	process {
+		foreach ($object in $InputObject) {
+			# Case 1: Find-PSFModule
+			if ($object.PSObject.TypeNames -contains 'PSFramework.NuGet.ModuleInfo') {
+				New-ModuleTarget -Object $object.Object -Cmdlet $Cmdlet
+			}
+			# Case 2: Find-Module
+			# Case 3: Find-PSResource
+			# Case 4: Get-Module
+			else {
+				New-ModuleTarget -Object $object -Cmdlet $Cmdlet
+			}
+		}
+		foreach ($nameEntry in $Name) {
+			New-ModuleTarget -Name $nameEntry -Version $Version -Prerelease:$Prerelease -Cmdlet $Cmdlet
+		}
+	}
+}
\ No newline at end of file
diff --git a/PSFramework.NuGet/internal/functions/Get/Resolve-RemotePath.ps1 b/PSFramework.NuGet/internal/functions/Get/Resolve-RemotePath.ps1
new file mode 100644
index 0000000..c0b4c65
--- /dev/null
+++ b/PSFramework.NuGet/internal/functions/Get/Resolve-RemotePath.ps1
@@ -0,0 +1,171 @@
+function Resolve-RemotePath {
+	<#
+	.SYNOPSIS
+		Test for target paths on remote computers.
+	
+	.DESCRIPTION
+		Test for target paths on remote computers.
+
+		Has differentiated error handling (see description on TargetHandling or examples),
+		in order to ensure proper tracking of all parallely processed targets.
+	
+	.PARAMETER Path
+		The paths to check.
+	
+	.PARAMETER ComputerName
+		The computers to check the paths on.
+		Supports established PSSession objects.
+	
+	.PARAMETER PathHandling
+		Whether all specified paths must exist on a target computer, or whether a single finding counts as success.
+		Defaults to: All
+	
+	.PARAMETER TargetHandling
+		How the command should handle unsuccessful computer targets:
+		All unsuccessful checks lead to a non-terminating exception.
+		However, depending on this parameter, a forced terminating exception might be thrown:
+		- "All": Even a single unsuccessful computer leads to terminal errors.
+		- "Any": If no target was successful, terminate
+		- "None": Never terminate
+		Defaults to: None
+	
+	.PARAMETER Cmdlet
+		The $PSCmdlet variable of the caller.
+		As this is an internal utility command, this allows it to terminate in the context of the calling command and remain invisible to the user.
+	
+	.EXAMPLE
+		PS C:\> Resolve-RemotePath -Path C:\Temp -ComputerName $computers
+
+		Checks for C:\Temp on all computers in $computers
+		Will not generate any terminating errors.
+
+	.EXAMPLE
+		PS C:\> Resolve-RemotePath -Path C:\Temp -ComputerName $computers -TargetHandling All
+
+		Checks for C:\Temp on all computers in $computers
+		If even a single computer cannot be reached or does not have the path, this will terminate the command.
+
+	.EXAMPLE
+		PS C:\> Resolve-RemotePath -Path C:\Temp, C:\Tmp -ComputerName $computers -TargetHandling All -PathHandling Any
+
+		Checks for C:\Temp or C:\Tmp on all computers in $computers
+		Each computer is considered successful, if one of the two paths exist on it.
+		If even a single computer is not successful - has neither path or cannot be reached - this command will terminate.
+
+	.EXAMPLE
+		PS C:\> Resolve-RemotePath -Path C:\Temp, C:\Tmp -ComputerName $computers -TargetHandling Any -PathHandling Any -ErrorAction SilentlyContinue
+
+		Checks for C:\Temp or C:\Tmp on all computers in $computers
+		Each computer is considered successful, if one of the two paths exist on it.
+		This command will continue unbothered, so long as at least one computer is successful.
+	#>
+	[CmdletBinding()]
+	param (
+		[Parameter(Mandatory = $true)]
+		[string[]]
+		$Path,
+
+		[AllowEmptyCollection()]
+		[AllowNull()]
+		[PSFComputer[]]
+		$ComputerName,
+
+		[ValidateSet('All', 'Any')]
+		[string]
+		$PathHandling = 'All',
+
+		[ValidateSet('All', 'Any', 'None')]
+		[string]
+		$TargetHandling = 'None',
+
+		$Cmdlet = $PSCmdlet
+	)
+	begin {
+		#region Implementing Code
+		$code = {
+			param ($Data)
+
+			$pathResults = foreach ($path in $Data.Paths) {
+				[PSCustomObject]@{
+					ComputerName = $env:COMPUTERNAME
+					Path         = $path
+					Exists       = Test-Path -Path $path
+				}
+			}
+
+			$result = [PSCustomObject]@{
+				ComputerName = $env:COMPUTERNAME
+				Path         = $Data.Path
+				Results      = $pathResults
+				ExistsAll    = @($pathResults).Where{ -not $_.Exists }.Count -lt 1
+				ExistsAny    = @($pathResults).Where{ $_.Exists }.Count -gt 0
+				Success      = $null
+				Error        = $null
+			}
+			if ($Data.PathHandling -eq 'All') { $result.Success = $result.ExistsAll }
+			else { $result.Success = $result.ExistsAny }
+
+			if (-not $result.Success) {
+				$message = "[$env:COMPUTERNAME] Path not found: $(@($pathResults).Where{ -not $_.Exists }.ForEach{ "'$($_.Path)'" } -join ', ')"
+				$result.Error = [System.Management.Automation.ErrorRecord]::new(
+					[System.Exception]::new($message),
+					'PathNotFound',
+					[System.Management.Automation.ErrorCategory]::ObjectNotFound,
+					@(@($pathResults).Where{ -not $_.Exists }.ForEach{ "'$($_.Path)'" })
+				)
+			}
+
+			$result
+		}
+		#endregion Implementing Code
+
+		# Passing a single array-argument as a hashtable is more reliable
+		$data = @{ Paths = $Path; PathHandling = $PathHandling }
+	}
+	process {
+		#region Collect Test-Results
+		if (-not $ComputerName) {
+			$testResult = & $code $data
+		}
+		else {
+			$failed = $null
+			$testResult = Invoke-PSFCommand -ComputerName $ComputerName -ScriptBlock $code -ArgumentList $data -ErrorAction SilentlyContinue -ErrorVariable failed
+			$failedResults = foreach ($failedTarget in $failed) {
+				[PSCustomObject]@{
+					ComputerName = $failedTarget.TargetObject
+					Path         = $Path
+					Results      = @()
+					ExistsAll    = $null
+					ExistsAny    = $null
+					Success      = $false
+					Error        = $failedTarget
+				}
+			}
+			$testResult = @($testResult) + @($failedResults) | Remove-PSFNull
+		}
+		#endregion Collect Test-Results
+
+		foreach ($result in $testResult) {
+			[PSFramework.Object.ObjectHost]::AddScriptMethod($result, 'ToString', { '{0}: {1}' -f $this.ComputerName, ($this.Path -join ' | ') })
+			if ($result.Success) { continue }
+
+			if (-not $result.Results) {
+				Write-PSFMessage -String 'Resolve-RemotePath.Error.UnReached' -StringValues $result.ComputerName, ($Path -join ' | ') -Tag fail, connect -Target $result
+			}
+			else {
+				Write-PSFMessage -String 'Resolve-RemotePath.Error.NotFound' -StringValues $result.ComputerName, (@($result.Results).Where{-not $_.Exists}.Path -join ' | ') -Tag fail, notfound -Target $result
+			}
+
+			$Cmdlet.WriteError($result.Error)
+		}
+
+		if ($TargetHandling -eq 'All' -and @($testResult).Where{ -not $_.Success }.Count -gt 0) {
+			Stop-PSFFunction -String 'Resolve-RemotePath.Fail.NotAll' -StringValues ($Path -join ' | ') -EnableException $true -Cmdlet $PSCmdlet
+		}
+		if ($TargetHandling -eq 'Any' -and @($testResult).Where{ $_.Success }.Count -eq 0) {
+			Stop-PSFFunction -String 'Resolve-RemotePath.Fail.NotAny' -StringValues ($Path -join ' | ') -EnableException $true -Cmdlet $PSCmdlet
+		}
+
+		$testResult
+	}
+}
\ No newline at end of file
diff --git a/PSFramework.NuGet/internal/functions/Get/Resolve-Repository.ps1 b/PSFramework.NuGet/internal/functions/Get/Resolve-Repository.ps1
new file mode 100644
index 0000000..acad14e
--- /dev/null
+++ b/PSFramework.NuGet/internal/functions/Get/Resolve-Repository.ps1
@@ -0,0 +1,52 @@
+function Resolve-Repository {
+	<#
+	.SYNOPSIS
+		Resolves the PowerShell Repository to use, including their order.
+	
+	.DESCRIPTION
+		Resolves the PowerShell Repository to use, including their order.
+		This differs from Get-PSFRepository by throwing a terminating exception in case no repository was found.
+	
+	.PARAMETER Name
+		Names of the Repositories to lookup.
+		Can be multiple, can use wildcards.
+	
+	.PARAMETER Type
+		Whether to return PSGet V2, V3 or all repositories.
+		Defaults to: "All"
+	
+	.PARAMETER Cmdlet
+		The $PSCmdlet variable of the caller.
+		As this is an internal utility command, this allows it to terminate in the context of the calling command and remain invisible to the user.
+	
+	.EXAMPLE
+		PS C:\> Resolve-Repository -Name PSGallery, Contoso -Cmdlet $PSCmdlet
+
+		Returns all repositories instances named PSGallery or Contoso, whether registered in V2 or V3
+
+	.EXAMPLE
+		Ps C:\> Resolve-Repository -Name PSGallery -Type V3 -Cmdlet $PSCmdlet
+
+		Returns the PSGet V3 instance of the PSGallery repository.
+	#>
+	[CmdletBinding()]
+	param (
+		[Parameter(Mandatory = $true)]
+		[string[]]
+		$Name,
+
+		[ValidateSet('All', 'V2', 'V3')]
+		[string]
+		$Type = 'All',
+
+		$Cmdlet = $PSCmdlet
+	)
+	process {
+		$repos = Get-PSFRepository -Name $Name -Type $Type
+
+		if (-not $repos) {
+			Stop-PSFFunction -String 'Resolve-Repository.Error.NoRepo' -StringValues ($Name -join ', '), $Type -EnableException $true -Cmdlet $Cmdlet -Category ObjectNotFound
+		}
+		$repos
+	}
+}
\ No newline at end of file
diff --git a/PSFramework.NuGet/internal/functions/Get/Save-StagingModule.ps1 b/PSFramework.NuGet/internal/functions/Get/Save-StagingModule.ps1
new file mode 100644
index 0000000..9da0147
--- /dev/null
+++ b/PSFramework.NuGet/internal/functions/Get/Save-StagingModule.ps1
@@ -0,0 +1,247 @@
+function Save-StagingModule {
+	[CmdletBinding()]
+	param (
+		[object[]]
+		$InstallData,
+		
+		[string]
+		$Path,
+
+		[object[]]
+		$Repositories,
+
+		[AllowNull()]
+		[PSCredential]
+		$Credential,
+
+		[switch]
+		$SkipDependency,
+
+		[switch]
+		$AuthenticodeCheck,
+
+		$Cmdlet = $PSCmdlet 
+	)
+	begin {
+		#region Implementing Functions
+		function Save-StagingModuleV2 {
+			[CmdletBinding()]
+			param (
+				$Repository,
+
+				$Item,
+
+				[string]
+				$Path,
+
+				[AllowNull()]
+				[PSCredential]
+				$Credential,
+
+				[switch]
+				$SkipDependency,
+
+				[switch]
+				$AuthenticodeCheck
+			)
+
+			Write-PSFMessage -String 'Save-StagingModule.SavingV2.Start' -StringValues $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+
+			$callSpecifics = @{
+				AcceptLicense = $true
+				ErrorAction   = 'Stop'
+				Repository    = $Repository.Name
+			}
+			if ($Credential) { $callSpecifics.Credential = $Credential }
+
+			$result = [PSCustomObject]@{
+				Success        = $false
+				Error          = $null
+				ModuleName     = $Item.Name
+				ModuleVersion  = $item.Version
+				RepositoryName = $Repository.Name
+				RepositoryType = $Repository.Type
+			}
+
+			$tempDirectory = New-PSFTempDirectory -Name StagingSub -ModuleName PSFramework.NuGet
+			$param = $Item.v2Param
+			# 1) Save to temp folder
+			try { Save-Module @param -Path $tempDirectory @callSpecifics }
+			catch {
+				Write-PSFMessage -String 'Save-StagingModule.SavingV2.Error.Download' -StringValues $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item -Tag fail, save -ErrorRecord $_
+				$result.Error = $_
+
+				Remove-PSFTempItem -Name StagingSub -ModuleName PSFramework.NuGet
+				Write-PSFMessage -String 'Save-StagingModule.SavingV2.Done' -StringValues $result.Success, $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+				return $result
+			}
+			# 2) Remove redundant modules
+			if ($SkipDependency) {
+				# V2 Does not support saving without its dependencies coming along, so we cleanup in pre-staging
+				try { Get-ChildItem -Path $tempDirectory | Where-Object Name -NE $Item.Name | Remove-Item -Force -Recurse -ErrorAction Stop }
+				catch {
+					Write-PSFMessage -String 'Save-StagingModule.SavingV2.Error.DependencyCleanup' -StringValues $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item -Tag fail, cleanup -ErrorRecord $_
+					$result.Error = $_
+	
+					Remove-PSFTempItem -Name StagingSub -ModuleName PSFramework.NuGet
+					Write-PSFMessage -String 'Save-StagingModule.SavingV2.Done' -StringValues $result.Success, $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+					return $result
+				}
+			}
+			# 3) Verify Signature
+			if ($AuthenticodeCheck) {
+				$signatures = foreach ($moduleBase in Get-ChildItem -Path $tempDirectory) {
+					Get-PSFModuleSignature -Path (Get-Item -Path "$moduleBase\*").FullName
+				}
+				foreach ($signature in $signatures) {
+					Write-PSFMessage -String 'Save-StagingModule.SavingV2.SignatureCheck' -StringValues $signature.Name, $signature.Version, $signature.IsSigned -Target $signature
+				}
+
+				if ($unsigned = @($signatures).Where{ -not $_.IsSigned }) {
+					$result.Error = [System.Management.Automation.ErrorRecord]::new(
+						[System.Exception]::new("Modules are not signed by a trusted code signer: $($unsigned.Name -join ', ')"),
+						'NotTrusted',
+						[System.Management.Automation.ErrorCategory]::SecurityError,
+						$unsigned
+					)
+					Write-PSFMessage -String 'Save-StagingModule.SavingV2.Error.Unsigned' -StringValues $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item -Tag fail, signed -ErrorRecord $result.Error
+					Remove-PSFTempItem -Name StagingSub -ModuleName PSFramework.NuGet
+					Write-PSFMessage -String 'Save-StagingModule.SavingV2.Done' -StringValues $result.Success, $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+					return $result
+				}
+			}
+			# 4) Move to Staging
+			try { Get-ChildItem -Path $tempDirectory | Copy-Item -Destination $Path -Recurse -Force -ErrorAction Stop }
+			catch {
+				Write-PSFMessage -String 'Save-StagingModule.SavingV2.Error.Transfer' -StringValues $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item -Tag fail, save -ErrorRecord $_
+				$result.Error = $_
+
+				Remove-PSFTempItem -Name StagingSub -ModuleName PSFramework.NuGet
+				Write-PSFMessage -String 'Save-StagingModule.SavingV2.Done' -StringValues $result.Success, $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+				return $result
+			}
+
+			Remove-PSFTempItem -Name StagingSub -ModuleName PSFramework.NuGet
+			$result.Success = $true
+			Write-PSFMessage -String 'Save-StagingModule.SavingV2.Done' -StringValues $result.Success, $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+			$result
+		}
+
+		function Save-StagingModuleV3 {
+			[CmdletBinding()]
+			param (
+				$Repository,
+
+				$Item,
+
+				[string]
+				$Path,
+
+				[AllowNull()]
+				[PSCredential]
+				$Credential,
+
+				[switch]
+				$SkipDependency,
+
+				[switch]
+				$AuthenticodeCheck
+			)
+
+			Write-PSFMessage -String 'Save-StagingModule.SavingV3.Start' -StringValues $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+
+			$callSpecifics = @{
+				AcceptLicense = $true
+				ErrorAction   = 'Stop'
+				Repository    = $Repository.Name
+			}
+			if ($Credential) { $callSpecifics.Credential = $Credential }
+			if ($SkipDependency) { $callSpecifics.SkipDependencyCheck = $true }
+
+			$result = [PSCustomObject]@{
+				Success        = $false
+				Error          = $null
+				ModuleName     = $Item.Name
+				ModuleVersion  = $item.Version
+				RepositoryName = $Repository.Name
+				RepositoryType = $Repository.Type
+			}
+
+			$tempDirectory = New-PSFTempDirectory -Name StagingSub -ModuleName PSFramework.NuGet
+			$param = $Item.v3Param
+			# 1) Save to temp folder
+			try { Save-PSResource @param -Path $tempDirectory @callSpecifics }
+			catch {
+				Write-PSFMessage -String 'Save-StagingModule.SavingV3.Error.Download' -StringValues $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item -Tag fail, save -ErrorRecord $_
+				$result.Error = $_
+
+				Remove-PSFTempItem -Name StagingSub -ModuleName PSFramework.NuGet
+				Write-PSFMessage -String 'Save-StagingModule.SavingV3.Done' -StringValues $result.Success, $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+				return $result
+			}
+			# 2) Verify Signature
+			if ($AuthenticodeCheck) {
+				$signatures = foreach ($moduleBase in Get-ChildItem -Path $tempDirectory) {
+					Get-PSFModuleSignature -Path (Get-Item -Path "$moduleBase\*").FullName
+				}
+				foreach ($signature in $signatures) {
+					Write-PSFMessage -String 'Save-StagingModule.SavingV3.SignatureCheck' -StringValues $signature.Name, $signature.Version, $signature.IsSigned -Target $signature
+				}
+
+				if ($unsigned = @($signatures).Where{ -not $_.IsSigned }) {
+					$result.Error = [System.Management.Automation.ErrorRecord]::new(
+						[System.Exception]::new("Modules are not signed by a trusted code signer: $($unsigned.Name -join ', ')"),
+						'NotTrusted',
+						[System.Management.Automation.ErrorCategory]::SecurityError,
+						$unsigned
+					)
+					Write-PSFMessage -String 'Save-StagingModule.SavingV3.Error.Unsigned' -StringValues $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item -Tag fail, signed -ErrorRecord $result.Error
+					Remove-PSFTempItem -Name StagingSub -ModuleName PSFramework.NuGet
+					Write-PSFMessage -String 'Save-StagingModule.SavingV3.Done' -StringValues $result.Success, $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+					return $result
+				}
+			}
+			# 3) Move to Staging
+			try { Get-ChildItem -Path $tempDirectory | Copy-Item -Destination $Path -Recurse -Force -ErrorAction Stop }
+			catch {
+				Write-PSFMessage -String 'Save-StagingModule.SavingV3.Error.Transfer' -StringValues $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item -Tag fail, save -ErrorRecord $_
+				$result.Error = $_
+
+				Remove-PSFTempItem -Name StagingSub -ModuleName PSFramework.NuGet
+				Write-PSFMessage -String 'Save-StagingModule.SavingV3.Done' -StringValues $result.Success, $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+				return $result
+			}
+
+			Remove-PSFTempItem -Name StagingSub -ModuleName PSFramework.NuGet
+			$result.Success = $true
+			Write-PSFMessage -String 'Save-StagingModule.SavingV3.Done' -StringValues $result.Success, $Item.Name, $Item.Version, $Repository.Name, $Repository.Type -Target $Item
+			$result
+		}
+		#endregion Implementing Functions
+
+		$common = @{
+			SkipDependency    = $SkipDependency
+			AuthenticodeCheck = $AuthenticodeCheck
+			Path              = $Path
+			Credential        = $Credential
+		}
+	}
+	process {
+		:item foreach ($installItem in $InstallData) {
+			$saveResults = foreach ($repository in $Repositories | Set-PSFObjectOrder -Property Priority, '>Type') {
+				$saveResult = switch ($repository.Type) {
+					V2 { Save-StagingModuleV2 -Repository $repository -Item $installItem @common }
+					V3 { Save-StagingModuleV3 -Repository $repository -Item $installItem @common }
+					default { Stop-PSFFunction -String 'Save-StagingModule.Error.UnknownRepoType' -StringValues $repository.Type, $repository.Name -Target $repository -Cmdlet $Cmdlet -EnableException $true }
+				}
+				if ($saveResult.Success) { continue item }
+				$saveResult
+			}
+			# Only reached if no repository was successful
+			foreach ($result in $saveResults) {
+				$Cmdlet.WriteError($result.Error)
+			}
+			Stop-PSFFunction -String 'Save-StagingModule.Error.SaveFailed' -StringValues $installItem.Name, $installItem.Version, (@($repository).ForEach{ '{0} ({1})' -f $_.Name, $_.Type } -join ', ') -Target $installItem -Cmdlet $Cmdlet -EnableException $true
+		}
+	}
+}
\ No newline at end of file
diff --git a/PSFramework.NuGet/internal/functions/Resolve-GetParameter.ps1 b/PSFramework.NuGet/internal/functions/Resolve-GetParameter.ps1
deleted file mode 100644
index 9d4e902..0000000
--- a/PSFramework.NuGet/internal/functions/Resolve-GetParameter.ps1
+++ /dev/null
@@ -1,45 +0,0 @@
-function Resolve-GetParameter {
-	[OutputType([hashtable])]
-	[CmdletBinding()]
-	param (
-		[Parameter(Mandatory = $true)]
-		[string]
-		$Command,
-
-		[Parameter(Mandatory = $true)]
-		$Values,
-
-		[bool]
-		$GetV3
-	)
-
-	process {
-		switch ($Command) {
-			#region Save Module
-			'Save-Module' {
-				#region V3
-				if ($GetV3) {
-
-				}
-				#endregion V3
-
-				#region V2
-				else {
-					$parameters = @((Get-Command Save-Module).Parameters.Keys | Write-Output) + @('ModuleName', 'ModuleVersion')
-					$hashtables = $Values | ConvertTo-PSFHashtable -Include $parameters -Remap @{
-						ModuleName = 'Name'
-						ModuleVersion = 'MinimumVersion'
-					}
-					$result = @{}
-					foreach ($hashtable in $hashtables) { $result += $hashtable }
-					$result
-				}
-				#endregion V3
-			}
-			#endregion Save Module
-			default {
-				throw "Command not supported: $Command"
-			}
-		}
-	}
-}
\ No newline at end of file