diff --git a/.gitmodules b/.gitmodules index 8a49b569c4..6fc1171a3e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "src/libpsl-native"] - path = src/libpsl-native - url = https://github.com/PowerShell/psl-native.git [submodule "src/omi"] path = src/omi url = https://github.com/Microsoft/omi.git @@ -11,8 +8,12 @@ [submodule "src/windows-build"] path = src/windows-build url = https://github.com/PowerShell/psl-windows-build.git -[submodule "src/Microsoft.PowerShell.Linux.Host/Modules/Pester"] - path = src/Microsoft.PowerShell.Linux.Host/Modules/Pester +[submodule "src/Modules/Pester"] + path = src/Modules/Pester url = https://github.com/PowerShell/psl-pester.git branch = develop ignore = dirty +[submodule "src/libpsl-native/test/googletest"] + path = src/libpsl-native/test/googletest + url = https://github.com/google/googletest.git + ignore = dirty diff --git a/.travis.yml b/.travis.yml index 8e78e7449b..0ea3ad8e41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,16 @@ language: cpp sudo: required dist: trusty -cache: - apt: true - directories: - - $HOME/.nuget git: submodules: false before_install: - git config --global url.git@github.com:.insteadOf https://github.com/ - - git submodule update --init --recursive -- src/libpsl-native src/windows-build src/Microsoft.PowerShell.Linux.Host/Modules/Pester - - sudo sh -c 'echo "deb [arch=amd64] http://apt-mo.trafficmanager.net/repos/dotnet/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list' - - sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 - - sudo apt-get -qq update - - sudo apt-get install -y dotnet=1.0.0.001675-1 + - git submodule update --init -- src/windows-build src/Modules/Pester src/libpsl-native/test/googletest + - ./bootstrap.sh - ./download.sh - - sudo dpkg -i powershell.deb + - sudo dpkg -i ./powershell.deb script: - - powershell -c "Import-Module ./PowerShellGitHubDev.psm1; Start-PSBuild" - - ./pester.sh - - ./xunit.sh + - powershell -c "Import-Module ./PowerShellGitHubDev.psm1; Start-PSBuild; Start-PSxUnit; Start-PSPester" notifications: slack: secure: sKYd4n61+ZFzGZuWGUl8V1kN0NM16wRVOFVlNhlFCwnkrEsKROb++EvXf5uwnKuzxkhEjvPWO+UFgeshQDoR93y4s5YLfhC5JupK4nUzjPzWs208KTrh8u/x9MY8X6Ojxi85EEAiku5GzMoMlkucSStZUYwbIfnelzqdw8uoRwmm2MW4XCPwsuEuDUVghyiva0Mdx1G6MopCrK8T96WywJXT3chhfZQgVt+sQCBt9g+2kjDaObKrzG0P07IVK43ZpDgnu6AoxlyBzIx9mJH2Oa/tki3/kTO72Wcp3ps3qvmiStADamzVKR9p1VlWCLWAd6VOehxuByCGEyujpzk135Wud2DZYO+8LD6inZVhFe3Wt5pCU9BDXZppiATfMCqgXEH7nK54pEn79yHcjthRJ2+Z9ot7As2fu3RSBmTAi8nRP0fxRyX/jctR3S6P0qt0y1ynx9nzBfhmhPQW0PMVazWS/nruQIvK/3iiYXjZxM5bBwIvabmwV00EYeTdbL6ufXWNgQcG1ZWkDsi2I3vst/ytUbHwaFYg83bXWpxg9DCzJeWLVUvE5/3NfBxRAuCTot/fgTEA9IYScvrlL7Q/bT0cOt0vEM98MPf1UO+WP85uxhsRgHtwDEo+jMaL6ZFkPhlV6mmmED4NdY2//a571cLNXdnuMAze5O3TWGBG53g= diff --git a/PowerShellGitHubDev.psm1 b/PowerShellGitHubDev.psm1 index fa22c2fe26..1b6605fcd1 100644 --- a/PowerShellGitHubDev.psm1 +++ b/PowerShellGitHubDev.psm1 @@ -17,19 +17,19 @@ try { $IsWindows = $true } -function Start-PSBuild -{ + +function Start-PSBuild { [CmdletBinding(DefaultParameterSetName='CoreCLR')] param( [switch]$Restore, [switch]$Clean, - [string]$Output, # These runtimes must match those in project.json # We do not use ValidateScript since we want tab completion [ValidateSet("ubuntu.14.04-x64", "centos.7.1-x64", "win7-x64", + "win81-x64", "win10-x64", "osx.10.10-x64", "osx.10.11-x64")] @@ -44,185 +44,213 @@ function Start-PSBuild [Parameter(ParameterSetName='FullCLR')] [ValidateSet("Debug", - "Release")] - [string]$msbuildConfiguration = "Release" + "Release")] + [string]$msbuildConfiguration = "Release" ) - function precheck([string]$command, [string]$missedMessage) - { - $c = Get-Command $command -ErrorAction SilentlyContinue - if (-not $c) - { - Write-Warning $missedMessage - return $false - } - else - { - return $true - } - } - - function log([string]$message) - { - Write-Host -Foreground Green $message - } - - # simplify ParameterSetNames, set output - if ($PSCmdlet.ParameterSetName -eq 'FullCLR') - { + # simplify ParameterSetNames + if ($PSCmdlet.ParameterSetName -eq 'FullCLR') { $FullCLR = $true } - if (-not $Output) - { - if ($FullCLR) { $Output = "$PSScriptRoot/binFull" } else { $Output = "$PSScriptRoot/bin" } - } - # verify we have all tools in place to do the build $precheck = precheck 'dotnet' "Build dependency 'dotnet' not found in PATH! See: https://dotnet.github.io/getting-started/" - if ($FullCLR) - { + if ($FullCLR) { # cmake is needed to build powershell.exe $precheck = $precheck -and (precheck 'cmake' 'cmake not found. You can install it from https://chocolatey.org/packages/cmake.portable') - + # msbuild is needed to build powershell.exe # msbuild is part of .NET Framework, we can try to get it from well-known location. - if (-not (Get-Command -Name msbuild -ErrorAction Ignore)) - { + if (-not (Get-Command -Name msbuild -ErrorAction Ignore)) { $env:path += ";${env:SystemRoot}\Microsoft.Net\Framework\v4.0.30319" } $precheck = $precheck -and (precheck 'msbuild' 'msbuild not found. Install Visual Studio 2015.') - } - - if (-not $precheck) { return } + } elseif ($IsLinux -Or $IsOSX) { + $InstallCommand = if ($IsLinux) { + 'apt-get' + } elseif ($IsOSX) { + 'brew' + } - # handle clean - if ($Clean) { - Remove-Item -Force -Recurse $Output -ErrorAction SilentlyContinue + foreach ($Dependency in 'cmake', 'make', 'g++') { + $precheck = $precheck -and (precheck $Dependency "Build dependency '$Dependency' not found. Run '$InstallCommand install $Dependency'") + } } - New-Item -Force -Type Directory $Output | Out-Null + if (-Not $Runtime) { + $Runtime = dotnet --info | % { + if ($_ -match "RID") { + $_ -split "\s+" | Select-Object -Last 1 + } + } + + if (-Not $Runtime) { + Write-Warning "Could not determine Runtime Identifier, please update dotnet" + $precheck = $false + } else { + log "Runtime not specified, using $Runtime" + } + } + + # Abort if any precheck failed + if (-not $precheck) { + return + } # define key build variables - if ($FullCLR) - { + if ($FullCLR) { $Top = "$PSScriptRoot\src\Microsoft.PowerShell.ConsoleHost" - $framework = 'net451' + $Framework = 'net451' + } else { + $Top = "$PSScriptRoot/src/Microsoft.PowerShell.Host" + $Framework = 'netstandardapp1.5' } - else - { - $Top = "$PSScriptRoot/src/Microsoft.PowerShell.Linux.Host" - $framework = 'netstandardapp1.5' + + if ($IsLinux -Or $IsOSX) { + $Configuration = "Linux" + $Executable = "powershell" + } else { + $Configuration = "Debug" + $Executable = "powershell.exe" } + $Arguments = @() + $Arguments += "--framework", $Framework + $Arguments += "--configuration", $Configuration + $Arguments += "--runtime", $Runtime + + # FullCLR only builds a library, so there is no runtime component + if ($FullCLR) { + $script:Output = [IO.Path]::Combine($Top, "bin", $Configuration, $Framework, $Executable) + } else { + $script:Output = [IO.Path]::Combine($Top, "bin", $Configuration, $Framework, $Runtime, $Executable) + } + Write-Verbose "script:Output is $script:Output" + # handle Restore if ($Restore -Or -Not (Test-Path "$Top/project.lock.json")) { log "Run dotnet restore" - $Arguments = @("--verbosity") + $RestoreArguments = @("--verbosity") if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) { - $Arguments += "Info" } else { $Arguments += "Warning" } + $RestoreArguments += "Info" + } else { + $RestoreArguments += "Warning" + } - if ($Runtime) { $Arguments += "--runtime", $Runtime } + $RestoreArguments += "$PSScriptRoot" - $Arguments += "$PSScriptRoot" - - dotnet restore $Arguments + dotnet restore $RestoreArguments } # Build native components - if (-not $FullCLR) - { - if ($IsLinux -Or $IsOSX) { - log "Start building native components" - $InstallCommand = if ($IsLinux) { "apt-get" } elseif ($IsOSX) { "brew" } - foreach ($Dependency in "cmake", "g++") { - if (-Not (Get-Command $Dependency -ErrorAction SilentlyContinue)) { - throw "Build dependency '$Dependency' not found in PATH! Run '$InstallCommand install $Dependency'" - } - } - - $Ext = if ($IsLinux) { "so" } elseif ($IsOSX) { "dylib" } - $Native = "$PSScriptRoot/src/libpsl-native" - $Lib = "$Top/libpsl-native.$Ext" - Write-Verbose "Building $Lib" - - try { - Push-Location $Native - cmake -DCMAKE_BUILD_TYPE=Debug . - make -j - make test - } finally { - Pop-Location - } - - if (-Not (Test-Path $Lib)) { throw "Compilation of $Lib failed" } + if ($IsLinux -Or $IsOSX) { + $Ext = if ($IsLinux) { + "so" + } elseif ($IsOSX) { + "dylib" } - } - else - { + + $Native = "$PSScriptRoot/src/libpsl-native" + $Lib = "$Top/libpsl-native.$Ext" + log "Start building $Lib" + + try { + Push-Location $Native + cmake -DCMAKE_BUILD_TYPE=Debug . + make -j + make test + } finally { + Pop-Location + } + + if (-Not (Test-Path $Lib)) { + throw "Compilation of $Lib failed" + } + } elseif ($FullCLR) { log "Start building native powershell.exe" - $build = "$PSScriptRoot/build" - if ($Clean) { - Remove-Item -Force -Recurse $build -ErrorAction SilentlyContinue - } - mkdir $build -ErrorAction SilentlyContinue - try - { - Push-Location $build + try { + Push-Location .\src\powershell-native - if ($cmakeGenerator) - { - cmake -G $cmakeGenerator ..\src\powershell-native - } - else - { - cmake ..\src\powershell-native + if ($cmakeGenerator) { + cmake -G $cmakeGenerator . + } else { + cmake . } + msbuild powershell.vcxproj /p:Configuration=$msbuildConfiguration - cp -rec $msbuildConfiguration\* $Output + + } finally { + Pop-Location } - finally { Pop-Location } } - log "Building PowerShell" - $Arguments = "--framework", $framework, "--output", $Output - if ($IsLinux -Or $IsOSX) { $Arguments += "--configuration", "Linux" } - if ($Runtime) { $Arguments += "--runtime", $Runtime } - - if ($FullCLR) - { - # there is a problem with code signing: - # AssemblyKeyFileAttribute file path cannot be correctly located, if `dotnet publish $TOP` syntax is used - # we workaround it with calling `dotnet publish` from $TOP directory instead. + try { + # Relative paths do not work well if cwd is not changed to project + log "Run `dotnet build $Arguments` from $pwd" Push-Location $Top - } - else - { - $Arguments += $Top + dotnet build $Arguments + log "PowerShell output: $script:Output" + } finally { + Pop-Location } - Write-Verbose "Run dotnet publish $Arguments from $pwd" +} - # this try-finally is part of workaround about AssemblyKeyFileAttribute issue - try - { - dotnet publish $Arguments + +function Get-PSOutput { + [CmdletBinding()]param() + if (-Not $Output) { + throw '$script:Output is not defined, run Start-PSBuild' } - finally - { - if ($FullCLR) { Pop-Location } + + $Output +} + + +function Start-PSPester { + [CmdletBinding()]param( + [string]$Flags = '-EnableExit -OutputFile pester-tests.xml -OutputFormat NUnitXml', + [string]$Tests = "*", + [ValidateScript({ Test-Path -PathType Container $_})] + [string]$Directory = "$PSScriptRoot/test/powershell" + ) + + & (Get-PSOutput) -c "Invoke-Pester $Flags $Directory/$Tests" + if ($LASTEXITCODE -ne 0) { + throw "$LASTEXITCODE Pester tests failed" } } -function Start-PSPackage -{ + +function Start-PSxUnit { + [CmdletBinding()]param() + if ($IsWindows) { + throw "xUnit tests are only currently supported on Linux / OS X" + } + + $Content = Split-Path -Parent (Get-PSOutput) + $Arguments = "--configuration", "Linux" + try { + Push-Location $PSScriptRoot/test/csharp + dotnet build $Arguments + Copy-Item -ErrorAction SilentlyContinue -Recurse -Path $Content/* -Include Modules,libpsl-native* -Destination "./bin/Linux/netstandardapp1.5/ubuntu.14.04-x64" + dotnet test $Arguments + if ($LASTEXITCODE -ne 0) { + throw "$LASTEXITCODE xUnit tests failed" + } + } finally { + Pop-Location + } +} + +function Start-PSPackage { # PowerShell packages use Semantic Versioning http://semver.org/ # # Ubuntu and OS X packages are supported. - param( + [CmdletBinding()]param( [string]$Version, [int]$Iteration = 1, [ValidateSet("deb", "osxpkg", "rpm")] @@ -285,8 +313,8 @@ function Start-PSPackage "$PSScriptRoot/package/powershell=/usr/local/bin" } -function Start-DevPSGitHub -{ + +function Start-DevPSGitHub { param( [switch]$ZapDisable, [string[]]$ArgumentList = '', @@ -295,27 +323,23 @@ function Start-DevPSGitHub [switch]$NoNewWindow ) - try - { - if ($LoadProfile -eq $false) - { + try { + if ($LoadProfile -eq $false) { $ArgumentList = @('-noprofile') + $ArgumentList } $env:DEVPATH = $binDir - if ($ZapDisable) - { + if ($ZapDisable) { $env:COMPLUS_ZapDisable = 1 } - if (-Not (Test-Path $binDir\powershell.exe.config)) - { + if (-Not (Test-Path $binDir\powershell.exe.config)) { $configContents = @" - - - + + + "@ $configContents | Out-File -Encoding Ascii $binDir\powershell.exe.config @@ -333,71 +357,29 @@ function Start-DevPSGitHub } Start-Process @startProcessArgs - } - finally - { + } finally { ri env:DEVPATH - if ($ZapDisable) - { + if ($ZapDisable) { ri env:COMPLUS_ZapDisable } } } -## this function is from Dave Wyatt's answer on -## http://stackoverflow.com/questions/22002748/hashtables-from-convertfrom-json-have-different-type-from-powershells-built-in-h -function Convert-PSObjectToHashtable -{ - param ( - [Parameter(ValueFromPipeline)] - $InputObject - ) - - process - { - if ($null -eq $InputObject) { return $null } - - if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) - { - $collection = @( - foreach ($object in $InputObject) { Convert-PSObjectToHashtable $object } - ) - - Write-Output -NoEnumerate $collection - } - elseif ($InputObject -is [psobject]) - { - $hash = @{} - - foreach ($property in $InputObject.PSObject.Properties) - { - $hash[$property.Name] = Convert-PSObjectToHashtable $property.Value - } - - $hash - } - else - { - $InputObject - } - } -} <# .EXAMPLE Copy-SubmoduleFiles # copy files FROM submodule TO src/ folders .EXAMPLE Copy-SubmoduleFiles -ToSubmodule # copy files FROM src/ folders TO submodule #> function Copy-SubmoduleFiles { - + [CmdletBinding()] param( [string]$mappingFilePath = "$PSScriptRoot/mapping.json", [switch]$ToSubmodule ) - - if (-not (Test-Path $mappingFilePath)) - { + + if (-not (Test-Path $mappingFilePath)) { throw "Mapping file not found in $mappingFilePath" } @@ -405,47 +387,38 @@ function Copy-SubmoduleFiles { # mapping.json assumes the root folder Push-Location $PSScriptRoot - try - { + try { $m.GetEnumerator() | % { - if ($ToSubmodule) - { + if ($ToSubmodule) { cp $_.Value $_.Key -Verbose:$Verbose - } - else - { + } else { mkdir (Split-Path $_.Value) -ErrorAction SilentlyContinue > $null - cp $_.Key $_.Value -Verbose:$Verbose + cp $_.Key $_.Value -Verbose:$Verbose } } - } - finally - { + } finally { Pop-Location } } + <# - .EXAMPLE Create-MappingFile # create mapping.json in the root folder from project.json files +.EXAMPLE Create-MappingFile # create mapping.json in the root folder from project.json files #> -function New-MappingFile -{ +function New-MappingFile { param( [string]$mappingFilePath = "$PSScriptRoot/mapping.json", [switch]$IgnoreCompileFiles, [switch]$Ignoreresource ) - function Get-MappingPath([string]$project, [string]$path) - { - if ($project -match 'TypeCatalogGen') - { + function Get-MappingPath([string]$project, [string]$path) { + if ($project -match 'TypeCatalogGen') { return Split-Path $path -Leaf } - - if ($project -match 'Microsoft.Management.Infrastructure') - { + + if ($project -match 'Microsoft.Management.Infrastructure') { return Split-Path $path -Leaf } @@ -456,8 +429,7 @@ function New-MappingFile # assumes the root folder Push-Location $PSScriptRoot - try - { + try { $projects = ls .\src\ -Recurse -Depth 2 -Filter 'project.json' $projects | % { $project = Split-Path $_.FullName @@ -465,8 +437,7 @@ function New-MappingFile if (-not $IgnoreCompileFiles) { $json.compileFiles | % { if ($_) { - if (-not $_.EndsWith('AssemblyInfo.cs')) - { + if (-not $_.EndsWith('AssemblyInfo.cs')) { $fullPath = Join-Path $project (Get-MappingPath -project $project -path $_) $mapping[$_.Replace('../', 'src/')] = ($fullPath.Replace("$($pwd.Path)\",'')).Replace('\', '/') } @@ -485,38 +456,19 @@ function New-MappingFile } } } - } - finally - { + } finally { Pop-Location } Set-Content -Value ($mapping | ConvertTo-Json) -Path $mappingFilePath -Encoding Ascii } -function Get-InvertedOrderedMap -{ - param( - $h - ) - $res = [ordered]@{} - foreach ($q in $h.GetEnumerator()) { - if ($res.Contains($q.Value)) - { - throw "Cannot invert hashtable: duplicated key $($q.Value)" - } - - $res[$q.Value] = $q.Key - } - return $res -} <# -.EXAMPLE Send-GitDiffToSd -diffArg1 45555786714d656bd31cbce67dbccb89c433b9cb -diffArg2 45555786714d656bd31cbce67dbccb89c433b9cb~1 -pathToAdmin d:\e\ps_dev\admin +.EXAMPLE Send-GitDiffToSd -diffArg1 45555786714d656bd31cbce67dbccb89c433b9cb -diffArg2 45555786714d656bd31cbce67dbccb89c433b9cb~1 -pathToAdmin d:\e\ps_dev\admin Apply a signle commit to admin folder #> -function Send-GitDiffToSd -{ +function Send-GitDiffToSd { param( [Parameter(Mandatory)] [string]$diffArg1, @@ -533,32 +485,88 @@ function Send-GitDiffToSd $affectedFiles = git diff --name-only $diffArg1 $diffArg2 $rev = Get-InvertedOrderedMap $m foreach ($file in $affectedFiles) { - if ($rev.Contains) - { + if ($rev.Contains) { $sdFilePath = Join-Path $pathToAdmin $rev[$file].Substring('src/monad/'.Length) $diff = git diff $diffArg1 $diffArg2 -- $file - if ($diff) - { + if ($diff) { Write-Host -Foreground Green "Apply patch to $sdFilePath" Set-Content -Value $diff -Path $env:TEMP\diff -Encoding Ascii - if ($WhatIf) - { + if ($WhatIf) { Write-Host -Foreground Green "Patch content" cat $env:TEMP\diff + } else { + & $patchPath --binary -p1 $sdFilePath $env:TEMP\diff } - else - { - & $patchPath --binary -p1 $sdFilePath $env:TEMP\diff - } - } - else - { + } else { Write-Host -Foreground Green "No changes in $file" } - } - else - { + } else { Write-Host -Foreground Green "Ignore changes in $file, because there is no mapping for it" } - } + } +} + + +function script:log([string]$message) { + Write-Host -Foreground Green $message +} + + +function script:precheck([string]$command, [string]$missedMessage) { + $c = Get-Command $command -ErrorAction SilentlyContinue + if (-not $c) { + Write-Warning $missedMessage + return $false + } else { + return $true + } +} + + +function script:Get-InvertedOrderedMap { + param( + $h + ) + $res = [ordered]@{} + foreach ($q in $h.GetEnumerator()) { + if ($res.Contains($q.Value)) { + throw "Cannot invert hashtable: duplicated key $($q.Value)" + } + + $res[$q.Value] = $q.Key + } + return $res +} + + +## this function is from Dave Wyatt's answer on +## http://stackoverflow.com/questions/22002748/hashtables-from-convertfrom-json-have-different-type-from-powershells-built-in-h +function script:Convert-PSObjectToHashtable { + param ( + [Parameter(ValueFromPipeline)] + $InputObject + ) + + process { + if ($null -eq $InputObject) { return $null } + + if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) { + $collection = @( + foreach ($object in $InputObject) { Convert-PSObjectToHashtable $object } + ) + + Write-Output -NoEnumerate $collection + } elseif ($InputObject -is [psobject]) { + $hash = @{} + + foreach ($property in $InputObject.PSObject.Properties) + { + $hash[$property.Name] = Convert-PSObjectToHashtable $property.Value + } + + $hash + } else { + $InputObject + } + } } diff --git a/appveyor.yml b/appveyor.yml index e10ecacea6..6b01530a3e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,9 +6,6 @@ environment: priv_key: secure: -cache: - - '%LocalAppData%\Microsoft\dotnet' - notifications: - provider: Slack incoming_webhook: @@ -20,9 +17,9 @@ install: - ps: $fileContent += "`n-----END RSA PRIVATE KEY-----`n" - ps: Set-Content c:\users\appveyor\.ssh\id_rsa $fileContent - git config --global url.git@github.com:.insteadOf https://github.com/ - - git submodule update --init --recursive -- src/windows-build src/Microsoft.PowerShell.Linux.Host/Modules/Pester + - git submodule update --init -- src/windows-build src/Modules/Pester - ps: Invoke-WebRequest -Uri https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0/scripts/obtain/install.ps1 -OutFile install.ps1 - - ps: ./install.ps1 -version 1.0.0.001888 + - ps: ./install.ps1 -version 1.0.0-beta-002198 build_script: - ps: | @@ -38,15 +35,17 @@ test_script: $ErrorActionPreference = 'Stop' # # CoreCLR + $env:CoreOutput = "$pwd\src\Microsoft.PowerShell.Host\bin\Debug\netstandardapp1.5\win81-x64" Write-Host -Foreground Green 'Run CoreCLR tests' $testResultsFile = "$pwd\TestsResults.xml" - .\bin\powershell.exe --noprofile -c "Import-Module .\bin\Modules\Microsoft.PowerShell.Platform; Invoke-Pester test/powershell -OutputFormat NUnitXml -OutputFile $testResultsFile" + & ("$env:CoreOutput\powershell.exe") -c "Invoke-Pester test/powershell -OutputFormat NUnitXml -OutputFile $testResultsFile" (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) # # FullCLR + $env:FullOutput = "$pwd\src\Microsoft.PowerShell.ConsoleHost\bin\Debug\net451" Write-Host -Foreground Green 'Run FullCLR tests' $testResultsFileFullCLR = "$pwd\TestsResults.FullCLR.xml" - Start-DevPSGitHub -binDir $pwd\binFull -NoNewWindow -ArgumentList '-command', "Import-Module .\src\Microsoft.PowerShell.Linux.Host\Modules\Pester; Import-Module .\bin\Modules\Microsoft.PowerShell.Platform; Invoke-Pester test/fullCLR -OutputFormat NUnitXml -OutputFile $testResultsFileFullCLR" + Start-DevPSGitHub -binDir $env:FullOutput -NoNewWindow -ArgumentList '-command', "Import-Module .\src\Modules\Pester; Import-Module .\bin\Modules\Microsoft.PowerShell.Platform; Invoke-Pester test/fullCLR -OutputFormat NUnitXml -OutputFile $testResultsFileFullCLR" (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFileFullCLR)) # # Fail the build, if tests failed @@ -71,8 +70,8 @@ on_finish: $zipFilePath = Join-Path $pwd "$name.zip" $zipFileFullPath = Join-Path $pwd "$name.FullCLR.zip" Add-Type -assemblyname System.IO.Compression.FileSystem - [System.IO.Compression.ZipFile]::CreateFromDirectory("$pwd\bin", $zipFilePath) - [System.IO.Compression.ZipFile]::CreateFromDirectory("$pwd\binFull", $zipFileFullPath) + [System.IO.Compression.ZipFile]::CreateFromDirectory($env:CoreOutput, $zipFilePath) + [System.IO.Compression.ZipFile]::CreateFromDirectory($env:FullOutput, $zipFileFullPath) @( # You can add other artifacts here diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000000..599321730c --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +echo "Installing build dependencies" + +curl http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add - +echo "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.6 main" | sudo tee /etc/apt/sources.list.d/llvm.list +sudo apt-get update -qq + +sudo apt-get install -y wget make g++ cmake \ + libc6 libgcc1 libstdc++6 \ + libcurl3 libgssapi-krb5-2 libicu52 liblldb-3.6 liblttng-ust0 libssl1.0.0 libunwind8 libuuid1 zlib1g clang-3.5 + +wget -P /tmp https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-host-ubuntu-x64.latest.deb +sudo dpkg -i /tmp/dotnet-host-ubuntu-x64.latest.deb + +wget -P /tmp https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-sharedframework-ubuntu-x64.latest.deb +sudo dpkg -i /tmp/dotnet-sharedframework-ubuntu-x64.latest.deb + +wget -P /tmp https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-sdk-ubuntu-x64.latest.deb +sudo dpkg -i /tmp/dotnet-sdk-ubuntu-x64.latest.deb diff --git a/build.sh b/build.sh index 5287ae1bb4..149910f03f 100755 --- a/build.sh +++ b/build.sh @@ -5,7 +5,7 @@ hash cmake 2>/dev/null || { echo >&2 "No cmake, please run 'sudo apt-get install hash g++ 2>/dev/null || { echo >&2 "No g++, please run 'sudo apt-get install g++'"; exit 1; } hash dotnet 2>/dev/null || { echo >&2 "No dotnet, please visit https://dotnet.github.io/getting-started/"; exit 1; } -TOP="$(pwd)/src/Microsoft.PowerShell.Linux.Host" +TOP="$(pwd)/src/Microsoft.PowerShell.Host" # Test for lock file test -r "$TOP/project.lock.json" || { echo >&2 "Please run 'dotnet restore' to download .NET Core packages"; exit 2; } diff --git a/docs/debugging/README.md b/docs/debugging/README.md index e1aaee6d71..2994048d17 100644 --- a/docs/debugging/README.md +++ b/docs/debugging/README.md @@ -42,3 +42,19 @@ provide a PID. (Please be careful not to commit such a change). [vscode]: https://code.visualstudio.com/ [OmniSharp]: https://github.com/OmniSharp/omnisharp-vscode [vscclrdebugger]: http://aka.ms/vscclrdebugger + +corehost +-------- + +The native executable prouduced by .NET CLI will produce trace output +if launched with `COREHOST_TRACE=1 ./powershell`. + +CoreCLR PAL +----------- + +The native code in the CLR has debug channels to selectively output +information to the console. These are controlled by the +`PAL_DBG_CHANNELS`, e.g., `export PAL_DBG_CHANNELS="+all.all"`, as +detailed in the `dbgmsg.h` [header][]. + +[header]: https://github.com/dotnet/coreclr/blob/release/1.0.0-rc2/src/pal/src/include/pal/dbgmsg.h diff --git a/mapping.json b/mapping.json index 8311b1361e..2f4ed00680 100644 --- a/mapping.json +++ b/mapping.json @@ -1180,5 +1180,26 @@ "src/monad/monad/nttargets/assemblies/nativemsh/pwrshcommon/WinSystemCallFacade.h": "src/powershell-native/nativemsh/pwrshcommon/WinSystemCallFacade.h", "src/monad/monad/nttargets/assemblies/nativemsh/pwrshexe/MainEntry.cpp": "src/powershell-native/nativemsh/pwrshexe/MainEntry.cpp", "src/monad/monad/nttargets/assemblies/nativemsh/pwrshexe/OutputWriter.h": "src/powershell-native/nativemsh/pwrshexe/OutputWriter.h", - "src/monad/monad/src/graphicalhost/visualstudiopublic.snk": "src/signing/visualstudiopublic.snk" + "src/monad/monad/src/graphicalhost/visualstudiopublic.snk": "src/signing/visualstudiopublic.snk", + "src/monad/monad/miscfiles/modules/AppxProvider/AppxProvider.psd1": "src/Modules/AppxProvider/AppxProvider.psd1", + "src/monad/monad/miscfiles/modules/AppxProvider/AppxProvider.psm1": "src/Modules/AppxProvider/AppxProvider.psm1", + "src/monad/monad/miscfiles/modules/AppxProvider/AppxProvider.Resource.psd1": "src/Modules/AppxProvider/AppxProvider.Resource.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Archive/ArchiveResources.psd1": "src/Modules/Microsoft.PowerShell.Archive/ArchiveResources.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1": "src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1": "src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Diagnostics/CoreClr/Microsoft.PowerShell.Diagnostics.psd1": "src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1": "src/Microsoft.PowerShell.ConsoleHost/Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1": "src/Modules/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1": "src/Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1": "src/Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Utility/CoreClr/Microsoft.PowerShell.Utility.psd1": "src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1": "src/Microsoft.PowerShell.ConsoleHost/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1", + "src/monad/monad/miscfiles/modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1": "src/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1", + "src/monad/monad/miscfiles/modules/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1": "src/Modules/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1", + "src/monad/monad/miscfiles/modules/PSDiagnostics/PSDiagnostics.psd1": "src/Modules/PSDiagnostics/PSDiagnostics.psd1", + "src/monad/monad/miscfiles/modules/PSDiagnostics/PSDiagnostics.psm1": "src/Modules/PSDiagnostics/PSDiagnostics.psm1", + "src/monad/monad/miscfiles/modules/PSGet/PSGet.Format.ps1xml": "src/Modules/PSGet/PSGet.Format.ps1xml", + "src/monad/monad/miscfiles/modules/PSGet/PSGet.psd1": "src/Modules/PSGet/PSGet.psd1", + "src/monad/monad/miscfiles/modules/PSGet/PSGet.Resource.psd1": "src/Modules/PSGet/PSGet.Resource.psd1", + "src/monad/monad/miscfiles/modules/PSGet/PSModule.psm1": "src/Modules/PSGet/PSModule.psm1" } diff --git a/pester.sh b/pester.sh deleted file mode 100755 index 400b3c79a5..0000000000 --- a/pester.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -test -x bin/powershell || { echo >&2 "No bin/powershell, please run './build.sh'"; exit 1; } - -./bin/powershell --noprofile -c "Import-Module Microsoft.PowerShell.Platform; Invoke-Pester test/powershell/$1 -OutputFile pester-tests.xml -OutputFormat NUnitXml -EnableExit" -failed_tests=$? - -# XML files are not executable -chmod -x pester-tests.xml - -# Return number of failed tests as exit code (more than 0 will be an error) -exit $failed_tests diff --git a/src/Microsoft.PowerShell.ConsoleHost/Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 b/src/Microsoft.PowerShell.ConsoleHost/Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 new file mode 100644 index 0000000000..d72eb9e283 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleHost/Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 @@ -0,0 +1,16 @@ +@{ +GUID="CA046F10-CA64-4740-8FF9-2565DBA61A4F" +Author="Microsoft Corporation" +CompanyName="Microsoft Corporation" +Copyright="© Microsoft Corporation. All rights reserved." +ModuleVersion="3.0.0.0" +PowerShellVersion="3.0" +CLRVersion="4.0" +AliasesToExport = @() +FunctionsToExport = @() +CmdletsToExport="Get-WinEvent", "Get-Counter", "Import-Counter", "Export-Counter", "New-WinEvent" +NestedModules="Microsoft.PowerShell.Commands.Diagnostics.dll" +TypesToProcess="GetEvent.types.ps1xml" +FormatsToProcess="Event.format.ps1xml","Diagnostics.format.ps1xml" +HelpInfoURI = 'http://go.microsoft.com/fwlink/?linkid=390783' +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Microsoft.PowerShell.ConsoleHost/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 new file mode 100644 index 0000000000..618763603d --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleHost/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -0,0 +1,33 @@ +@{ +GUID="1DA87E53-152B-403E-98DC-74D7B4D63D59" +Author="Microsoft Corporation" +CompanyName="Microsoft Corporation" +Copyright="© Microsoft Corporation. All rights reserved." +ModuleVersion="3.1.0.0" +PowerShellVersion="3.0" +CLRVersion="4.0" +CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide", + "Out-File", "Out-Printer", "Out-String", + "Out-GridView", "Get-FormatData", "Export-FormatData", "ConvertFrom-Json", "ConvertTo-Json", + "Invoke-RestMethod", "Invoke-WebRequest", "Register-ObjectEvent", "Register-EngineEvent", + "Wait-Event", "Get-Event", "Remove-Event", "Get-EventSubscriber", "Unregister-Event", + "New-Event", "Add-Member", "Add-Type", "Compare-Object", "ConvertTo-Html", "ConvertFrom-StringData", + "Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Invoke-Expression", + "Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random", "Get-UICulture", + "Get-Unique", "Export-PSSession", "Import-PSSession", "Import-Alias", "Import-LocalizedData", + "Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date", + "Start-Sleep", "Tee-Object", "Measure-Command", "Update-List", "Update-TypeData", "Update-FormatData", + "Remove-TypeData", "Get-TypeData", "Write-Host", "Write-Progress", "New-Object", "Select-Object", + "Group-Object", "Sort-Object", "Get-Variable", "New-Variable", "Set-Variable", "Remove-Variable", + "Clear-Variable", "Export-Clixml", "Import-Clixml", "ConvertTo-Xml", "Select-Xml", "Write-Debug", + "Write-Verbose", "Write-Warning", "Write-Error", "Write-Information", "Write-Output", "Set-PSBreakpoint", "Get-PSBreakpoint", + "Remove-PSBreakpoint", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack", + "Send-MailMessage", "Get-TraceSource", "Set-TraceSource", "Trace-Command", "Show-Command", "Unblock-File", + "Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug", "Get-RunspaceDebug", "Wait-Debugger", + "ConvertFrom-String", "Convert-String" +FunctionsToExport= "Get-FileHash", "New-TemporaryFile", "New-Guid", "Format-Hex", "Import-PowerShellDataFile", + "ConvertFrom-SddlString" +AliasesToExport= "CFS", "fhx" +NestedModules="Microsoft.PowerShell.Commands.Utility.dll","Microsoft.PowerShell.Utility.psm1" +HelpInfoURI = 'http://go.microsoft.com/fwlink/?linkid=390787' +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/project.json b/src/Microsoft.PowerShell.ConsoleHost/project.json index 0b99854b1d..999371d722 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/project.json +++ b/src/Microsoft.PowerShell.ConsoleHost/project.json @@ -14,6 +14,12 @@ "Microsoft.PowerShell.Commands.Utility": "1.0.0-*" }, + "content": [ + "Modules", + "../Modules", + "powershell.exe" + ], + "frameworks": { "net451": { } diff --git a/src/Microsoft.PowerShell.Linux.Host/.gitignore b/src/Microsoft.PowerShell.Host/.gitignore similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/.gitignore rename to src/Microsoft.PowerShell.Host/.gitignore diff --git a/src/Microsoft.PowerShell.Linux.Host/AssemblyInfo.cs b/src/Microsoft.PowerShell.Host/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/AssemblyInfo.cs rename to src/Microsoft.PowerShell.Host/AssemblyInfo.cs diff --git a/src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 b/src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 new file mode 100644 index 0000000000..dec59a24f7 --- /dev/null +++ b/src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 @@ -0,0 +1,13 @@ +@{ +GUID="CA046F10-CA64-4740-8FF9-2565DBA61A4F" +Author="Microsoft Corporation" +CompanyName="Microsoft Corporation" +Copyright="© Microsoft Corporation. All rights reserved." +ModuleVersion="3.0.0.0" +PowerShellVersion="3.0" +CmdletsToExport="Get-WinEvent", "New-WinEvent" +NestedModules="Microsoft.PowerShell.Commands.Diagnostics.dll" +TypesToProcess="..\..\GetEvent.types.ps1xml" +FormatsToProcess="..\..\Event.format.ps1xml" +HelpInfoURI = 'http://go.microsoft.com/fwlink/?linkid=390783' +} \ No newline at end of file diff --git a/src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 rename to src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 diff --git a/src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Platform/Microsoft.PowerShell.Platform.psd1 b/src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Platform/Microsoft.PowerShell.Platform.psd1 similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Platform/Microsoft.PowerShell.Platform.psd1 rename to src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Platform/Microsoft.PowerShell.Platform.psd1 diff --git a/src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Platform/Microsoft.PowerShell.Platform.psm1 b/src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Platform/Microsoft.PowerShell.Platform.psm1 similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Platform/Microsoft.PowerShell.Platform.psm1 rename to src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Platform/Microsoft.PowerShell.Platform.psm1 diff --git a/src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 rename to src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 diff --git a/src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 rename to src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 diff --git a/src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 b/src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 rename to src/Microsoft.PowerShell.Host/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 diff --git a/src/Microsoft.PowerShell.Linux.Host/Modules/PSDiagnostics/PSDiagnostics.psd1 b/src/Microsoft.PowerShell.Host/Modules/PSDiagnostics/PSDiagnostics.psd1 similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/Modules/PSDiagnostics/PSDiagnostics.psd1 rename to src/Microsoft.PowerShell.Host/Modules/PSDiagnostics/PSDiagnostics.psd1 diff --git a/src/Microsoft.PowerShell.Linux.Host/Modules/PSDiagnostics/PSDiagnostics.psm1 b/src/Microsoft.PowerShell.Host/Modules/PSDiagnostics/PSDiagnostics.psm1 similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/Modules/PSDiagnostics/PSDiagnostics.psm1 rename to src/Microsoft.PowerShell.Host/Modules/PSDiagnostics/PSDiagnostics.psm1 diff --git a/src/Microsoft.PowerShell.Linux.Host/PSL_profile.ps1 b/src/Microsoft.PowerShell.Host/PSL_profile.ps1 similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/PSL_profile.ps1 rename to src/Microsoft.PowerShell.Host/PSL_profile.ps1 diff --git a/src/Microsoft.PowerShell.Linux.Host/README.md b/src/Microsoft.PowerShell.Host/README.md similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/README.md rename to src/Microsoft.PowerShell.Host/README.md diff --git a/src/Microsoft.PowerShell.Linux.Host/host.cs b/src/Microsoft.PowerShell.Host/host.cs similarity index 99% rename from src/Microsoft.PowerShell.Linux.Host/host.cs rename to src/Microsoft.PowerShell.Host/host.cs index f4fa3e2538..5849c99c2f 100644 --- a/src/Microsoft.PowerShell.Linux.Host/host.cs +++ b/src/Microsoft.PowerShell.Host/host.cs @@ -1,4 +1,4 @@ -namespace Microsoft.PowerShell.Linux.Host +namespace Microsoft.PowerShell.Host { using System; using System.Globalization; diff --git a/src/Microsoft.PowerShell.Linux.Host/main.cs b/src/Microsoft.PowerShell.Host/main.cs similarity index 99% rename from src/Microsoft.PowerShell.Linux.Host/main.cs rename to src/Microsoft.PowerShell.Host/main.cs index cef1bb1a28..133b0c4280 100644 --- a/src/Microsoft.PowerShell.Linux.Host/main.cs +++ b/src/Microsoft.PowerShell.Host/main.cs @@ -1,4 +1,4 @@ -namespace Microsoft.PowerShell.Linux.Host +namespace Microsoft.PowerShell.Host { using System; using System.Collections.Generic; diff --git a/src/Microsoft.PowerShell.Linux.Host/project.json b/src/Microsoft.PowerShell.Host/project.json similarity index 76% rename from src/Microsoft.PowerShell.Linux.Host/project.json rename to src/Microsoft.PowerShell.Host/project.json index 0bdce2016b..1473ec4472 100644 --- a/src/Microsoft.PowerShell.Linux.Host/project.json +++ b/src/Microsoft.PowerShell.Host/project.json @@ -19,6 +19,7 @@ "content": [ "Modules", + "../Modules", "*_profile.ps1", "*.so", "*.dylib" @@ -28,5 +29,15 @@ "netstandardapp1.5": { "imports": [ "dnxcore50", "portable-net45+win8" ] } + }, + + "runtimes": { + "ubuntu.14.04-x64": { }, + "centos.7.1-x64": { }, + "win7-x64": { }, + "win81-x64": { }, + "win10-x64": { }, + "osx.10.10-x64": { }, + "osx.10.11-x64": { } } } diff --git a/src/Microsoft.PowerShell.Linux.Host/rawui.cs b/src/Microsoft.PowerShell.Host/rawui.cs similarity index 99% rename from src/Microsoft.PowerShell.Linux.Host/rawui.cs rename to src/Microsoft.PowerShell.Host/rawui.cs index 9b359a2daa..e4b2214205 100644 --- a/src/Microsoft.PowerShell.Linux.Host/rawui.cs +++ b/src/Microsoft.PowerShell.Host/rawui.cs @@ -10,7 +10,7 @@ using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; -namespace Microsoft.PowerShell.Linux.Host +namespace Microsoft.PowerShell.Host { // this is all from https://msdn.microsoft.com/en-us/library/ee706570%28v=vs.85%29.aspx diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Host/readline.cs similarity index 99% rename from src/Microsoft.PowerShell.Linux.Host/readline.cs rename to src/Microsoft.PowerShell.Host/readline.cs index f44bdbb764..8d6c4bda8d 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Host/readline.cs @@ -1,4 +1,4 @@ -namespace Microsoft.PowerShell.Linux.Host +namespace Microsoft.PowerShell.Host { using System; using System.Collections.ObjectModel; diff --git a/src/Microsoft.PowerShell.Linux.Host/ui.cs b/src/Microsoft.PowerShell.Host/ui.cs similarity index 99% rename from src/Microsoft.PowerShell.Linux.Host/ui.cs rename to src/Microsoft.PowerShell.Host/ui.cs index 04aed71c5c..ad325690a8 100644 --- a/src/Microsoft.PowerShell.Linux.Host/ui.cs +++ b/src/Microsoft.PowerShell.Host/ui.cs @@ -1,4 +1,4 @@ -namespace Microsoft.PowerShell.Linux.Host +namespace Microsoft.PowerShell.Host { using System; using System.Collections.Generic; diff --git a/src/Microsoft.PowerShell.Linux.Host/update-content.sh b/src/Microsoft.PowerShell.Linux.Host/update-content.sh deleted file mode 100755 index 967d10f98e..0000000000 --- a/src/Microsoft.PowerShell.Linux.Host/update-content.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# Types files -cp ../monad/monad/miscfiles/types/CoreClr/types.ps1xml . -cp ../monad/monad/miscfiles/types/CoreClr/typesv3.ps1xml . - -# Format files -cp ../monad/monad/miscfiles/display/Certificate.format.ps1xml . -cp ../monad/monad/miscfiles/display/Diagnostics.Format.ps1xml Diagnostics.format.ps1xml -cp ../monad/monad/miscfiles/display/DotNetTypes.format.ps1xml . -cp ../monad/monad/miscfiles/display/Event.format.ps1xml . -cp ../monad/monad/miscfiles/display/FileSystem.format.ps1xml . -cp ../monad/monad/miscfiles/display/Help.format.ps1xml . -cp ../monad/monad/miscfiles/display/HelpV3.format.ps1xml . -cp ../monad/monad/miscfiles/display/PowerShellCore.format.ps1xml . -cp ../monad/monad/miscfiles/display/PowerShellTrace.format.ps1xml . -cp ../monad/monad/miscfiles/display/Registry.format.ps1xml . -cp ../monad/monad/miscfiles/display/WSMan.format.ps1xml . - -mkdir Modules -cp -r ../monad/monad/miscfiles/modules/Microsoft.PowerShell.Utility Modules -UTILSCLR=Modules/Microsoft.PowerShell.Utility/CoreClr -mv $UTILSCLR/* Modules/Microsoft.PowerShell.Utility && rmdir $UTILSCLR -cp -r ../monad/monad/miscfiles/modules/Microsoft.PowerShell.Security Modules -cp -r ../monad/monad/miscfiles/modules/Microsoft.PowerShell.Management Modules -cp -r ../monad/monad/miscfiles/modules/PSDiagnostics Modules diff --git a/src/Modules/AppxProvider/AppxProvider.Resource.psd1 b/src/Modules/AppxProvider/AppxProvider.Resource.psd1 new file mode 100644 index 0000000000..f9c68ab194 Binary files /dev/null and b/src/Modules/AppxProvider/AppxProvider.Resource.psd1 differ diff --git a/src/Modules/AppxProvider/AppxProvider.psd1 b/src/Modules/AppxProvider/AppxProvider.psd1 new file mode 100644 index 0000000000..b9f746d7e6 Binary files /dev/null and b/src/Modules/AppxProvider/AppxProvider.psd1 differ diff --git a/src/Modules/AppxProvider/AppxProvider.psm1 b/src/Modules/AppxProvider/AppxProvider.psm1 new file mode 100644 index 0000000000..1c7a9f8ab1 --- /dev/null +++ b/src/Modules/AppxProvider/AppxProvider.psm1 @@ -0,0 +1,956 @@ +######################################################################################### +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# Appx Provider Module +# +######################################################################################### + +$script:ProviderName = "appx" +$script:AppxPackageExtension = ".appx" +$script:AppxManifestFile = "AppxManifest.xml" +$script:Architecture = "Architecture" +$script:ResourceId = "ResourceId" +$script:AppxPackageSources = $null +$script:AppxLocalPath="$env:LOCALAPPDATA\Microsoft\Windows\PowerShell\AppxProvider" +$script:AppxPackageSourcesFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:AppxLocalPath -ChildPath "AppxPackageSources.xml" +$Script:ResponseUri = "ResponseUri" +$Script:StatusCode = "StatusCode" +# Wildcard pattern matching configuration. +$script:wildcardOptions = [System.Management.Automation.WildcardOptions]::CultureInvariant -bor ` + [System.Management.Automation.WildcardOptions]::IgnoreCase +#Localized Data +Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename AppxProvider.Resource.psd1 + +#region Appx Provider APIs Implementation +function Get-PackageProviderName +{ + return $script:ProviderName +} + +function Initialize-Provider{ + param( + ) +} + +function Get-DynamicOptions +{ + param + ( + [Microsoft.PackageManagement.MetaProvider.PowerShell.OptionCategory] + $category + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Get-DynamicOptions')) + + switch($category) + { + Install { + Write-Output -InputObject (New-DynamicOption -Category $category -Name Architecture -ExpectedType String -IsRequired $false) + Write-Output -InputObject (New-DynamicOption -Category $category -Name ResourceId -ExpectedType String -IsRequired $false) + } + Package { + Write-Output -InputObject (New-DynamicOption -Category $category -Name Architecture -ExpectedType String -IsRequired $false) + Write-Output -InputObject (New-DynamicOption -Category $category -Name ResourceId -ExpectedType String -IsRequired $false) + } + } +} + +function Find-Package +{ + [CmdletBinding()] + param + ( + [string[]] + $names, + + [string] + $requiredVersion, + + [string] + $minimumVersion, + + [string] + $maximumVersion + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Find-Package')) + + $ResourceId = $null + $Architecture = $null + $Sources = @() + $streamedResults = @() + $namesParameterEmpty = (-not $names) -or (($names.Count -eq 1) -and ($names[0] -eq '')) + + Set-PackageSourcesVariable + + if($RequiredVersion -and $MinimumVersion) + { + + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.VersionRangeAndRequiredVersionCannotBeSpecifiedTogether ` + -ErrorId "VersionRangeAndRequiredVersionCannotBeSpecifiedTogether" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + if($RequiredVersion -or $MinimumVersion) + { + if(-not $names -or $names.Count -ne 1 -or (Test-WildcardPattern -Name $names[0])) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.VersionParametersAreAllowedOnlyWithSinglePackage ` + -ErrorId "VersionParametersAreAllowedOnlyWithSinglePackage" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + } + + $options = $request.Options + if($options) + { + foreach( $o in $options.Keys ) + { + Write-Debug ( "OPTION: {0} => {1}" -f ($o, $options[$o]) ) + } + + if($options.ContainsKey('Source')) + { + $SourceNames = $($options['Source']) + Write-Verbose ($LocalizedData.SpecifiedSourceName -f ($SourceNames)) + foreach($sourceName in $SourceNames) + { + if($script:AppxPackageSources.Contains($sourceName)) + { + $Sources += $script:AppxPackageSources[$sourceName] + } + else + { + $sourceByLocation = Get-SourceName -Location $sourceName + if ($sourceByLocation -ne $null) + { + $Sources += $script:AppxPackageSources[$sourceByLocation] + } + else + { + $message = $LocalizedData.PackageSourceNotFound -f ($sourceName) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PackageSourceNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $sourceName + } + } + } + } + else + { + Write-Verbose $LocalizedData.NoSourceNameIsSpecified + $script:AppxPackageSources.Values | Microsoft.PowerShell.Core\ForEach-Object { $Sources += $_ } + } + + if($options.ContainsKey($script:Architecture)) + { + $Architecture = $options[$script:Architecture] + } + if($options.ContainsKey($script:ResourceId)) + { + $ResourceId = $options[$script:ResourceId] + } + } + + foreach($source in $Sources) + { + $location = $source.SourceLocation + if($request.IsCanceled) + { + return + } + if(-not(Test-Path $location)) + { + $message = $LocalizedData.PathNotFound -f ($Location) + Write-Verbose $message + continue + } + + $packages = Get-AppxPackagesFromPath -path $location + foreach($pkg in $packages) + { + if($request.IsCanceled) + { + return + } + + $pkgManifest = Get-PackageManfiestData -PackageFullPath $pkg.FullName + if(-not $pkgManifest) + { + continue + } + + # $pkgManifest.Name has to match any of the supplied names, using PowerShell wildcards + if(-not($namesParameterEmpty)) + { + if(-not(($names | Microsoft.PowerShell.Core\ForEach-Object { if ($pkgManifest.Name -like $_){return $true; break} } -End {return $false}))) + { + continue + } + } + + # Version + if($RequiredVersion) + { + if($RequiredVersion -ne $pkgManifest.Version) + { + continue + } + } + else + { + if(-not((-not $MinimumVersion -or ($MinimumVersion -le $pkgManifest.Version)) -and + (-not $MaximumVersion -or ($MaximumVersion -ge $pkgManifest.Version)))) + { + continue + } + } + + + if($Architecture) + { + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $Architecture, $script:wildcardOptions + if(-not($wildcardPattern.IsMatch($pkgManifest.Architecture))) + { + continue + } + } + + if($ResourceId) + { + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $ResourceId, $script:wildcardOptions + if(-not($wildcardPattern.IsMatch($pkgManifest.ResourceId))) + { + continue + } + } + + $sid = New-SoftwareIdentityFromPackage -Package $pkgManifest -Source $source.Name + $fastPackageReference = $sid.fastPackageReference + if($streamedResults -notcontains $fastPackageReference) + { + $streamedResults += $fastPackageReference + Write-Output -InputObject $sid + } + } + } +} + +function Get-InstalledPackage +{ + [CmdletBinding()] + param + ( + [Parameter()] + [string] + $Name, + + [Parameter()] + [string] + $RequiredVersion, + + [Parameter()] + [string] + $MinimumVersion, + + [Parameter()] + [string] + $MaximumVersion + ) + + Write-Debug -Message ($LocalizedData.ProviderApiDebugMessage -f ('Get-InstalledPackage')) + + $Architecture = $null + $ResourceId = $null + + $options = $request.Options + if($options) + { + if($options.ContainsKey($script:Architecture)) + { + $Architecture = $options[$script:Architecture] + } + if($options.ContainsKey($script:ResourceId)) + { + $ResourceId = $options[$script:ResourceId] + } + } + + $params = @{} + if($Name) + { + $params.Add("Name", $Name) + } + $packages = Appx\Get-AppxPackage @params + + foreach($package in $packages) + { + if($RequiredVersion) + { + if($RequiredVersion -ne $package.Version) + { + continue + } + } + else + { + if(-not((-not $MinimumVersion -or ($MinimumVersion -le $package.Version)) -and + (-not $MaximumVersion -or ($MaximumVersion -ge $package.Version)))) + { + continue + } + } + + if($Architecture) + { + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $Architecture, $script:wildcardOptions + if(-not($wildcardPattern.IsMatch($package.Architecture))) + { + continue + } + } + if($ResourceId) + { + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $ResourceId,$script:wildcardOptions + if(-not($wildcardPattern.IsMatch($package.ResourceId))) + { + continue + } + } + + $sid = New-SoftwareIdentityFromPackage -Package $package + write-Output $sid + } +} + +function Install-Package +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $fastPackageReference + ) + + Write-Debug -Message ($LocalizedData.ProviderApiDebugMessage -f ('Install-Package')) + Write-Debug -Message ($LocalizedData.FastPackageReference -f $fastPackageReference) + + Appx\Add-AppxPackage -Path $fastPackageReference +} + +function UnInstall-Package +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $fastPackageReference + ) + + Write-Debug -Message ($LocalizedData.ProviderApiDebugMessage -f ('Uninstall-Package')) + Write-Debug -Message ($LocalizedData.FastPackageReference -f $fastPackageReference) + + Appx\Remove-AppxPackage -Package $fastPackageReference +} + +function Add-PackageSource +{ + [CmdletBinding()] + param + ( + [string] + $Name, + + [string] + $Location, + + [bool] + $Trusted + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Add-PackageSource')) + + Set-PackageSourcesVariable -Force + + if(-not (Microsoft.PowerShell.Management\Test-Path $Location) -and + -not (Test-WebUri -uri $Location)) + { + $LocationUri = [Uri]$Location + if($LocationUri.Scheme -eq 'file') + { + $message = $LocalizedData.PathNotFound -f ($Location) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Location + } + else + { + $message = $LocalizedData.InvalidWebUri -f ($Location, "Location") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Location + } + } + + if(Test-WildcardPattern $Name) + { + $message = $LocalizedData.PackageSourceNameContainsWildCards -f ($Name) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PackageSourceNameContainsWildCards" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Name + } + + $LocationString = Get-ValidPackageLocation -LocationString $Location -ParameterName "Location" + + # Check if Location is already registered with another Name + $existingSourceName = Get-SourceName -Location $LocationString + + if($existingSourceName -and + ($Name -ne $existingSourceName)) + { + $message = $LocalizedData.PackageSourceAlreadyRegistered -f ($existingSourceName, $Location, $Name) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PackageSourceAlreadyRegistered" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + + # Check if Name is already registered + if($script:AppxPackageSources.Contains($Name)) + { + $currentSourceObject = $script:AppxPackageSources[$Name] + $null = $script:AppxPackageSources.Remove($Name) + } + + # Add new package source + $packageSource = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + Name = $Name + SourceLocation = $LocationString + Trusted=$Trusted + Registered= $true + }) + + $script:AppxPackageSources.Add($Name, $packageSource) + $message = $LocalizedData.SourceRegistered -f ($Name, $LocationString) + Write-Verbose $message + + # Persist the package sources + Save-PackageSources + + # return the package source object. + Write-Output -InputObject (New-PackageSourceFromSource -Source $packageSource) + +} + +function Resolve-PackageSource +{ + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Resolve-PackageSource')) + + Set-PackageSourcesVariable + + $SourceName = $request.PackageSources + + if(-not $SourceName) + { + $SourceName = "*" + } + + foreach($src in $SourceName) + { + if($request.IsCanceled) + { + return + } + + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $src,$script:wildcardOptions + $sourceFound = $false + + $script:AppxPackageSources.GetEnumerator() | + Microsoft.PowerShell.Core\Where-Object {$wildcardPattern.IsMatch($_.Key)} | + Microsoft.PowerShell.Core\ForEach-Object { + $source = $script:AppxPackageSources[$_.Key] + $packageSource = New-PackageSourceFromSource -Source $source + Write-Output -InputObject $packageSource + $sourceFound = $true + } + + if(-not $sourceFound) + { + $sourceName = Get-SourceName -Location $src + if($sourceName) + { + $source = $script:AppxPackageSources[$sourceName] + $packageSource = New-PackageSourceFromSource -Source $source + Write-Output -InputObject $packageSource + } + elseif( -not (Test-WildcardPattern $src)) + { + $message = $LocalizedData.PackageSourceNotFound -f ($src) + Write-Error -Message $message -ErrorId "PackageSourceNotFound" -Category InvalidOperation -TargetObject $src + } + } + } +} + +function Remove-PackageSource +{ + param + ( + [string] + $Name + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Remove-PackageSource')) + + Set-PackageSourcesVariable -Force + + $SourcesToBeRemoved = @() + + foreach ($sourceName in $Name) + { + if($request.IsCanceled) + { + return + } + + # Check if $Name contains any wildcards + if(Test-WildcardPattern $sourceName) + { + $message = $LocalizedData.PackageSourceNameContainsWildCards -f ($sourceName) + Write-Error -Message $message -ErrorId "PackageSourceNameContainsWildCards" -Category InvalidOperation -TargetObject $sourceName + continue + } + + # Check if the specified package source name is in the registered package sources + if(-not $script:AppxPackageSources.Contains($sourceName)) + { + $message = $LocalizedData.PackageSourceNotFound -f ($sourceName) + Write-Error -Message $message -ErrorId "PackageSourceNotFound" -Category InvalidOperation -TargetObject $sourceName + continue + } + + $SourcesToBeRemoved += $sourceName + $message = $LocalizedData.PackageSourceUnregistered -f ($sourceName) + Write-Verbose $message + } + + # Remove the SourcesToBeRemoved + $SourcesToBeRemoved | Microsoft.PowerShell.Core\ForEach-Object { $null = $script:AppxPackageSources.Remove($_) } + + # Persist the package sources + Save-PackageSources +} +#endregion + +#region Common functions + +function Get-AppxPackagesFromPath +{ + param + ( + [Parameter(Mandatory=$true)] + $Path + ) + + $filterAppxPackages = "*"+$script:AppxPackageExtension + $packages = Get-ChildItem -path $Path -filter $filterAppxPackages + + return $packages + +} + +function Get-PackageManfiestData +{ + param + ( + [Parameter(Mandatory=$true)] + $PackageFullPath + ) + $guid = [System.Guid]::NewGuid().toString() + try + { + [System.IO.Compression.ZipFile]::ExtractToDirectory($PackageFullPath, "$env:TEMP\$guid") + } + catch + { + Write-Verbose( $LocalizedData.MetaDataExtractionFailed -f ($PackageFullPath) ) + return $null + } + + [xml] $packageManifest = Get-Content "$env:TEMP\$guid\$script:AppxManifestFile" -ErrorAction SilentlyContinue + if($packageManifest) + { + $Identity = $packageManifest.Package.Identity + $manifestData = new-object psObject -Property @{Name=$Identity.Name; Architecture=$Identity.ProcessorArchitecture; Publisher=$Identity.Publisher; Version=$Identity.Version; ResourceId=$Identity.resourceId; PackageFullName=$PackageFullPath} + Remove-Item -Path "$env:TEMP\$guid" -Recurse -Force -ErrorAction SilentlyContinue + return $manifestData + } + else + { + Write-Verbose ($LocalizedData.MetaDataExtractionFailed -f ($PackageFullPath) ) + } + return $null +} + +function New-FastPackageReference +{ + param + ( + [Parameter(Mandatory=$true)] + [string] + $PackageFullName + ) + return "$PackageFullName" +} + +function New-SoftwareIdentityFromPackage +{ + param + ( + [Parameter(Mandatory=$true)] + $Package, + + [string] + $Source + + ) + + $fastPackageReference = New-FastPackageReference -PackageFullName $Package.PackageFullName + + if(-not($Source)) + { + $Source = $Package.Publisher + } + $details = @{ + Publisher = $Package.Publisher + Architecture = $Package.Architecture + ResourceId = $Package.ResourceId + PackageFullName = $Package.PackageFullName + } + + $params = @{ + FastPackageReference = $fastPackageReference; + Name = $Package.Name; + Version = $Package.Version; + versionScheme = "MultiPartNumeric"; + Source = $source; + Details = $details; + } + + $sid = New-SoftwareIdentity @params + return $sid +} + +function Test-WebUri +{ + [CmdletBinding()] + [OutputType([bool])] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $uri + ) + + return ($uri.AbsoluteURI -ne $null) -and ($uri.Scheme -match '[http|https]') +} + +function Test-WildcardPattern +{ + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory=$true)] + [ValidateNotNull()] + $Name + ) + + return [System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Name) +} + +function DeSerialize-PSObject +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + $Path + ) + $filecontent = Microsoft.PowerShell.Management\Get-Content -Path $Path + [System.Management.Automation.PSSerializer]::Deserialize($filecontent) +} + +function Get-SourceName +{ + [CmdletBinding()] + [OutputType("string")] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Location + ) + + Set-PackageSourcesVariable + + foreach($source in $script:AppxPackageSources.Values) + { + if($source.SourceLocation -eq $Location) + { + return $source.Name + } + } +} + +function WebRequestApisAvailable +{ + $webRequestApiAvailable = $false + try + { + [System.Net.WebRequest] + $webRequestApiAvailable = $true + } + catch + { + } + return $webRequestApiAvailable +} + +function Ping-Endpoint +{ + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Endpoint + ) + + $results = @{} + + if(WebRequestApisAvailable) + { + $iss = [System.Management.Automation.Runspaces.InitialSessionState]::Create() + $iss.types.clear() + $iss.formats.clear() + $iss.LanguageMode = "FullLanguage" + + $WebRequestcmd = @' + try + {{ + $request = [System.Net.WebRequest]::Create("{0}") + $request.Method = 'GET' + $request.Timeout = 30000 + $response = [System.Net.HttpWebResponse]$request.GetResponse() + $response + $response.Close() + }} + catch [System.Net.WebException] + {{ + "Error:System.Net.WebException" + }} +'@ -f $EndPoint + + $ps = [powershell]::Create($iss).AddScript($WebRequestcmd) + $response = $ps.Invoke() + $ps.dispose() + + if ($response -ne "Error:System.Net.WebException") + { + $results.Add($Script:ResponseUri,$response.ResponseUri.ToString()) + $results.Add($Script:StatusCode,$response.StatusCode.value__) + } + } + else + { + $response = $null + try + { + $httpClient = New-Object 'System.Net.Http.HttpClient' + $response = $httpclient.GetAsync($endpoint) + } + catch + { + } + + if ($response -ne $null -and $response.result -ne $null) + { + $results.Add($Script:ResponseUri,$response.Result.RequestMessage.RequestUri.AbsoluteUri.ToString()) + $results.Add($Script:StatusCode,$response.result.StatusCode.value__) + } + } + return $results +} + +function Get-ValidPackageLocation +{ + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $LocationString, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $ParameterName + ) + + # Get the actual Uri from the Location + if(-not (Microsoft.PowerShell.Management\Test-Path $LocationString)) + { + $results = Ping-Endpoint -Endpoint $LocationString + + if ($results.ContainsKey("Exception")) + { + $Exception = $results["Exception"] + if($Exception) + { + $message = $LocalizedData.InvalidWebUri -f ($LocationString, $ParameterName) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -ExceptionObject $Exception ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + } + + if ($results.ContainsKey("ResponseUri")) + { + $LocationString = $results["ResponseUri"] + } + } + + return $LocationString +} + +function Set-PackageSourcesVariable +{ + param([switch]$Force) + + if(-not $script:AppxPackageSources -or $Force) + { + if(Microsoft.PowerShell.Management\Test-Path $script:AppxPackageSourcesFilePath) + { + $script:AppxPackageSources = DeSerialize-PSObject -Path $script:AppxPackageSourcesFilePath + } + else + { + $script:AppxPackageSources = [ordered]@{} + } + } +} + +function Save-PackageSources +{ + if($script:AppxPackageSources) + { + if(-not (Microsoft.PowerShell.Management\Test-Path $script:AppxLocalPath)) + { + $null = Microsoft.PowerShell.Management\New-Item -Path $script:AppxLocalPath ` + -ItemType Directory -Force ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false -WhatIf:$false + } + Microsoft.PowerShell.Utility\Out-File -FilePath $script:AppxPackageSourcesFilePath -Force -InputObject ([System.Management.Automation.PSSerializer]::Serialize($script:AppxPackageSources)) + } +} + +function New-PackageSourceFromSource +{ + param + ( + [Parameter(Mandatory)] + $Source + ) + + # create a new package source + $src = New-PackageSource -Name $Source.Name ` + -Location $Source.SourceLocation ` + -Trusted $Source.Trusted ` + -Registered $Source.Registered ` + + Write-Verbose ( $LocalizedData.PackageSourceDetails -f ($src.Name, $src.Location, $src.IsTrusted, $src.IsRegistered) ) + + # return the package source object. + Write-Output -InputObject $src +} +#endregion + +# Utility to throw an errorrecord +function ThrowError +{ + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCmdlet] + $CallerPSCmdlet, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ExceptionName, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ExceptionMessage, + + [System.Object] + $ExceptionObject, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ErrorId, + + [parameter(Mandatory = $true)] + [ValidateNotNull()] + [System.Management.Automation.ErrorCategory] + $ErrorCategory + ) + + $exception = New-Object $ExceptionName $ExceptionMessage; + $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject + $CallerPSCmdlet.ThrowTerminatingError($errorRecord) +} +#endregion + +Export-ModuleMember -Function Find-Package, ` + Install-Package, ` + Uninstall-Package, ` + Get-InstalledPackage, ` + Remove-PackageSource, ` + Resolve-PackageSource, ` + Add-PackageSource, ` + Get-DynamicOptions, ` + Initialize-Provider, ` + Get-PackageProviderName \ No newline at end of file diff --git a/src/Modules/Microsoft.PowerShell.Archive/ArchiveResources.psd1 b/src/Modules/Microsoft.PowerShell.Archive/ArchiveResources.psd1 new file mode 100644 index 0000000000..914f951df0 Binary files /dev/null and b/src/Modules/Microsoft.PowerShell.Archive/ArchiveResources.psd1 differ diff --git a/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 b/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 new file mode 100644 index 0000000000..859ea55124 --- /dev/null +++ b/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 @@ -0,0 +1,12 @@ +@{ +GUID="eb74e8da-9ae2-482a-a648-e96550fb8733" +Author="Microsoft Corporation" +CompanyName="Microsoft Corporation" +Copyright="© Microsoft Corporation. All rights reserved." +ModuleVersion="1.0.0.0" +FunctionsToExport = @('Compress-Archive', 'Expand-Archive') +CmdletsToExport = @() +AliasesToExport = @() +NestedModules="Microsoft.PowerShell.Archive.psm1" +HelpInfoURI = 'http://go.microsoft.com/fwlink/?LinkId=393254' +} diff --git a/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 b/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 new file mode 100644 index 0000000000..b301646d6e Binary files /dev/null and b/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 differ diff --git a/src/Modules/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 b/src/Modules/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 new file mode 100644 index 0000000000..9b3ecfc5f2 --- /dev/null +++ b/src/Modules/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 @@ -0,0 +1,14 @@ +@{ +GUID="56D66100-99A0-4FFC-A12D-EEE9A6718AEF" +Author="Microsoft Corporation" +CompanyName="Microsoft Corporation" +Copyright="© Microsoft Corporation. All rights reserved." +ModuleVersion="3.0.0.0" +PowerShellVersion="3.0" +CLRVersion="4.0" +AliasesToExport = @() +FunctionsToExport = @() +CmdletsToExport="Start-Transcript", "Stop-Transcript" +NestedModules="Microsoft.PowerShell.ConsoleHost.dll" +HelpInfoURI = 'http://go.microsoft.com/fwlink/?linkid=390784' +} diff --git a/src/Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 new file mode 100644 index 0000000000..425e64b78b --- /dev/null +++ b/src/Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 @@ -0,0 +1,99 @@ +@{ +GUID="EEFCB906-B326-4E99-9F54-8B4BB6EF3C6D" +Author="Microsoft Corporation" +CompanyName="Microsoft Corporation" +Copyright="© Microsoft Corporation. All rights reserved." +ModuleVersion="3.1.0.0" +PowerShellVersion="3.0" +CLRVersion="4.0" +NestedModules="Microsoft.PowerShell.Commands.Management.dll" +HelpInfoURI = 'http://go.microsoft.com/fwlink/?linkid=390785' +AliasesToExport = @("gcb", "scb") +FunctionsToExport = @() +CmdletsToExport=@("Add-Content", + "Clear-Content", + "Clear-ItemProperty", + "Join-Path", + "Convert-Path", + "Copy-ItemProperty", + "Get-EventLog", + "Clear-EventLog", + "Write-EventLog", + "Limit-EventLog", + "Show-EventLog", + "New-EventLog", + "Remove-EventLog", + "Get-ChildItem", + "Get-Content", + "Get-ItemProperty", + "Get-ItemPropertyValue", + "Get-WmiObject", + "Invoke-WmiMethod", + "Move-ItemProperty", + "Get-Location", + "Set-Location", + "Push-Location", + "Pop-Location", + "New-PSDrive", + "Remove-PSDrive", + "Get-PSDrive", + "Get-Item", + "New-Item", + "Set-Item", + "Remove-Item", + "Move-Item", + "Rename-Item", + "Copy-Item", + "Clear-Item", + "Invoke-Item", + "Get-PSProvider", + "New-ItemProperty", + "Split-Path", + "Test-Path", + "Get-Process", + "Stop-Process", + "Wait-Process", + "Debug-Process", + "Start-Process", + "Remove-ItemProperty", + "Remove-WmiObject", + "Rename-ItemProperty", + "Register-WmiEvent", + "Resolve-Path", + "Get-Service", + "Stop-Service", + "Start-Service", + "Suspend-Service", + "Resume-Service", + "Restart-Service", + "Set-Service", + "New-Service", + "Set-Content", + "Set-ItemProperty", + "Set-WmiInstance", + "Get-Transaction", + "Start-Transaction", + "Complete-Transaction", + "Undo-Transaction", + "Use-Transaction", + "New-WebServiceProxy", + "Get-HotFix", + "Test-Connection", + "Enable-ComputerRestore", + "Disable-ComputerRestore", + "Checkpoint-Computer", + "Get-ComputerRestorePoint", + "Restart-Computer", + "Stop-Computer", + "Restore-Computer", + "Add-Computer", + "Remove-Computer", + "Test-ComputerSecureChannel", + "Reset-ComputerMachinePassword", + "Rename-Computer", + "Get-ControlPanelItem", + "Show-ControlPanelItem", + "Clear-Recyclebin", + "Get-Clipboard", + "Set-Clipboard") +} diff --git a/src/Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 new file mode 100644 index 0000000000..a5a678d7b8 --- /dev/null +++ b/src/Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 @@ -0,0 +1,14 @@ +@{ +GUID="A94C8C7E-9810-47C0-B8AF-65089C13A35A" +Author="Microsoft Corporation" +CompanyName="Microsoft Corporation" +Copyright="© Microsoft Corporation. All rights reserved." +ModuleVersion="3.0.0.0" +PowerShellVersion="3.0" +CLRVersion="4.0" +AliasesToExport = @() +FunctionsToExport = @() +CmdletsToExport="Get-Acl", "Set-Acl", "Get-PfxCertificate", "Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "Get-AuthenticodeSignature", "Set-AuthenticodeSignature", "ConvertFrom-SecureString", "ConvertTo-SecureString", "Get-CmsMessage", "Unprotect-CmsMessage", "Protect-CmsMessage" , "New-FileCatalog" , "Test-FileCatalog" +NestedModules="Microsoft.PowerShell.Security.dll" +HelpInfoURI = 'http://go.microsoft.com/fwlink/?linkid=390786' +} diff --git a/src/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 b/src/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 new file mode 100644 index 0000000000..85eaf15336 --- /dev/null +++ b/src/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 @@ -0,0 +1,709 @@ +function Get-FileHash +{ + [CmdletBinding(DefaultParameterSetName = "Path", HelpURI = "http://go.microsoft.com/fwlink/?LinkId=517145")] + param( + [Parameter(Mandatory, ParameterSetName="Path", Position = 0)] + [System.String[]] + $Path, + + [Parameter(Mandatory, ParameterSetName="LiteralPath", ValueFromPipelineByPropertyName = $true)] + [Alias("PSPath")] + [System.String[]] + $LiteralPath, + + [Parameter(Mandatory, ParameterSetName="Stream")] + [System.IO.Stream] + $InputStream, + + [ValidateSet("SHA1", "SHA256", "SHA384", "SHA512", "MACTripleDES", "MD5", "RIPEMD160")] + [System.String] + $Algorithm="SHA256" + ) + + begin + { + # Construct the strongly-typed crypto object + + # First see if it has a FIPS algorithm + $hasherType = "System.Security.Cryptography.${Algorithm}CryptoServiceProvider" -as [Type] + if ($hasherType) + { + $hasher = $hasherType::New() + } + else + { + # Check if the type is supported in the current system + $algorithmType = "System.Security.Cryptography.${Algorithm}" -as [Type] + if ($algorithmType) + { + if ($Algorithm -eq "MACTripleDES") + { + $hasher = $algorithmType::New() + } + else + { + $hasher = $algorithmType::Create() + } + } + else + { + $errorId = "AlgorithmTypeNotSupported" + $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::AlgorithmTypeNotSupported -f $Algorithm + $exception = [System.InvalidOperationException]::New($errorMessage) + $errorRecord = [System.Management.Automation.ErrorRecord]::New($exception, $errorId, $errorCategory, $null) + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + } + + function GetStreamHash + { + param( + [System.IO.Stream] + $InputStream, + + [System.String] + $RelatedPath, + + [System.Security.Cryptography.HashAlgorithm] + $Hasher) + + # Compute file-hash using the crypto object + [Byte[]] $computedHash = $Hasher.ComputeHash($InputStream) + [string] $hash = [BitConverter]::ToString($computedHash) -replace '-','' + + if ($RelatedPath -eq $null) + { + $retVal = [PSCustomObject] @{ + Algorithm = $Algorithm.ToUpperInvariant() + Hash = $hash + } + } + else + { + $retVal = [PSCustomObject] @{ + Algorithm = $Algorithm.ToUpperInvariant() + Hash = $hash + Path = $RelatedPath + } + } + $retVal.psobject.TypeNames.Insert(0, "Microsoft.Powershell.Utility.FileHash") + $retVal + } + } + + process + { + if($PSCmdlet.ParameterSetName -eq "Stream") + { + GetStreamHash -InputStream $InputStream -RelatedPath $null -Hasher $hasher + } + else + { + $pathsToProcess = @() + if($PSCmdlet.ParameterSetName -eq "LiteralPath") + { + $pathsToProcess += Resolve-Path -LiteralPath $LiteralPath | Foreach-Object ProviderPath + } + if($PSCmdlet.ParameterSetName -eq "Path") + { + $pathsToProcess += Resolve-Path $Path | Foreach-Object ProviderPath + } + + foreach($filePath in $pathsToProcess) + { + if(Test-Path -LiteralPath $filePath -PathType Container) + { + continue + } + + try + { + # Read the file specified in $FilePath as a Byte array + [system.io.stream]$stream = [system.io.file]::OpenRead($filePath) + GetStreamHash -InputStream $stream -RelatedPath $filePath -Hasher $hasher + } + catch [Exception] + { + $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::FileReadError -f $FilePath, $_ + Write-Error -Message $errorMessage -Category ReadError -ErrorId "FileReadError" -TargetObject $FilePath + return + } + finally + { + if($stream) + { + $stream.Dispose() + } + } + } + } + } +} + +<# This cmdlet is used to create a new temporary file in $env:temp #> +function New-TemporaryFile +{ + [CmdletBinding( + HelpURI='http://go.microsoft.com/fwlink/?LinkId=526726', + SupportsShouldProcess=$true)] + [OutputType([System.IO.FileInfo])] + Param() + + Begin + { + try + { + if($PSCmdlet.ShouldProcess($env:TEMP)) + { + $tempFilePath = [System.IO.Path]::GetTempFileName() + } + } + catch + { + $errorRecord = [System.Management.Automation.ErrorRecord]::new($_.Exception,"NewTemporaryFileWriteError", "WriteError", $env:TEMP) + Write-Error -ErrorRecord $errorRecord + return + } + + if($tempFilePath) + { + Get-Item $tempFilePath + } + } +} + +<# This cmdlet is used to generate a new guid #> +function New-Guid +{ + [CmdletBinding(HelpURI='http://go.microsoft.com/fwlink/?LinkId=526920')] + [OutputType([System.Guid])] + Param() + + Begin + { + [Guid]::NewGuid() + } +} + +<############################################################################################ +# Format-Hex cmdlet helps in displaying the Hexadecimal equivalent of the input data. +############################################################################################> +function Format-Hex +{ + [CmdletBinding( + DefaultParameterSetName="Path", + HelpUri="http://go.microsoft.com/fwlink/?LinkId=526919")] + [Alias("fhx")] + [OutputType("Microsoft.PowerShell.Commands.ByteCollection")] + param + ( + [Parameter (Mandatory=$true, Position=0, ParameterSetName="Path")] + [ValidateNotNullOrEmpty()] + [string[]] $Path, + + [Parameter (Mandatory=$true, ParameterSetName="LiteralPath")] + [ValidateNotNullOrEmpty()] + [Alias("PSPath")] + [string[]] $LiteralPath, + + [Parameter(Mandatory=$true, ParameterSetName="ByInputObject", ValueFromPipeline=$true)] + [Object] $InputObject, + + [Parameter (ParameterSetName="ByInputObject")] + [ValidateSet("Ascii", "UTF32", "UTF7", "UTF8", "BigEndianUnicode", "Unicode")] + [string] $Encoding = "Ascii", + + [Parameter(ParameterSetName="ByInputObject")] + [switch]$Raw + ) + + begin + { + $bufferSize = 16 + $inputStreamArray = [System.Collections.ArrayList]::New() + <############################################################################################ + # The ConvertToHexadecimalHelper is a helper method used to fetch unicode bytes from the + # input data and display the hexadecimial representaion of the of the input data in bytes. + ############################################################################################> + function ConvertToHexadecimalHelper + { + param + ( + [Byte[]] $inputBytes, + [string] $path, + [Uint32] $offset + ) + + # This section is used to display the hexadecimal + # representaion of the of the input data in bytes. + if($inputBytes -ne $null) + { + $byteCollectionObject = [Microsoft.PowerShell.Commands.ByteCollection]::new($offset, $inputBytes, $path) + Write-Output -InputObject $byteCollectionObject + } + } + + <############################################################################################ + # The ProcessFileContent is a helper method used to fetch file contents in blocks and + # process it to support displaying hexadecimal formating of the fetched content. + ############################################################################################> + function ProcessFileContent + { + param + ( + [string] $filePath, + [boolean] $isLiteralPath + ) + + if($isLiteralPath) + { + $resolvedPaths = Resolve-Path -LiteralPath $filePath + } + else + { + $resolvedPaths = Resolve-Path -Path $filePath + } + + # If Path resolution has failed then a corresponding non-terminating error is + # written to the pipeline. We continue processing any remaining files. + if($resolvedPaths -eq $null) + { + return + } + + if($resolvedPaths.Count -gt 1) + { + # write a non-terminating error message indicating that path specified is resolving to multiple file system paths. + $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::FormatHexResolvePathError -f $filePath + Write-Error -Message $errorMessage -Category ([System.Management.Automation.ErrorCategory]::InvalidData) -ErrorId "FormatHexResolvePathError" + } + + $targetFilePath = $resolvedPaths.ProviderPath + + + if($targetFilePath -ne $null) + { + $buffer = [byte[]]::new($bufferSize) + + try + { + try + { + $currentFileStream = [System.IO.File]::Open($targetFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) + } + catch + { + # Failed to access the file. Write a non terminating error to the pipeline + # and move on with the remaining files. + $exception = $_.Exception + if($null -ne $_.Exception -and + $null -ne $_.Exception.InnerException) + { + $exception = $_.Exception.InnerException + } + + $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception,"FormatHexFileAccessError", ([System.Management.Automation.ErrorCategory]::ReadError), $targetFilePath) + $PSCmdlet.WriteError($errorRecord) + } + + if($null -ne $currentFileStream) + { + $srcStream = [System.IO.BinaryReader]::new($currentFileStream) + $displayHeader = $true + $offset = 0 + $blockCounter = 0 + while($numberOfBytesRead = $srcStream.Read($buffer, 0, $bufferSize)) + { + # send only the bytes that have been read + # if we send the whole buffer, we'll have extraneous bytes + # at the end of an incomplete group of 16 bytes + if ( $numberOfBytesRead -eq $bufferSize ) + { + # under some circumstances if we don't copy the buffer + # and the results are stored to a variable, the results are not + # correct and one object replicated in all the output objects + ConvertToHexadecimalHelper ($buffer.Clone()) $targetFilePath $offset + } + else + { + # handle the case of the partial (and probably last) buffer + $bytesReadBuffer = [byte[]]::New($numberOfBytesRead) + [Array]::Copy($buffer,0, $bytesReadBuffer,0,$numberOfBytesRead) + ConvertToHexadecimalHelper $bytesReadBuffer $targetFilePath $offset + } + $displayHeader = $false + $blockCounter++; + + # Updating the offset value. + $offset = $blockCounter*0x10 + } + } + } + finally + { + If($null -ne $currentFileStream) + { + $currentFileStream.Dispose() + } + If($null -ne $srcStream) + { + $srcStream.Dispose() + } + } + } + } + } + + process + { + switch($PSCmdlet.ParameterSetName) + { + "Path" + { + ProcessFileContent $Path $false + } + "LiteralPath" + { + ProcessFileContent $LiteralPath $true + } + "ByInputObject" + { + # If it's an actual byte array, then we directly use it for hexadecimal formatting. + if($InputObject -is [Byte[]]) + { + ConvertToHexadecimalHelper $InputObject $null + return + } + # if it's a single byte, we'll assume streaming + elseif($InputObject -is [byte]) + { + $null = $inputStreamArray.Add($InputObject) + } + # If the input data is of string type then directly get bytes out of it. + elseif($InputObject -is [string]) + { + # The ValidateSet arribute on the Encoding paramter makes sure that only + # valid values (supported on all paltforms where Format-Hex is avaliable) + # are allowed through user input. + $inputBytes = [Text.Encoding]::$Encoding.GetBytes($InputObject) + ConvertToHexadecimalHelper $inputBytes $null + return + } + elseif($InputObject -is [System.IO.FileSystemInfo]) + { + # If file path is provided as an input, use the file contents to show the hexadecimal format. + $filePath = ([System.IO.FileSystemInfo]$InputObject).FullName + ProcessFileContent $filePath $false + return + } + elseif($InputObject -is [int64]) + { + $inputBytes = [BitConverter]::GetBytes($InputObject) + $null = $inputStreamArray.AddRange($inputBytes) + } + elseif($InputObject -is [int64[]]) + { + foreach($i64 in $InputObject) + { + $inputBytes = [BitConverter]::GetBytes($i64) + $null = $inputStreamArray.AddRange($inputBytes) + } + } + elseif($InputObject -is [int]) + { + # If we get what appears as ints, it may not be what the user really wants. + # for example, if the user types a small set of numbers just to get their + # character representations, as follows: + # + # 170..180 | format-hex + # Path: + # 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F + #00000000 AA AB AC AD AE AF B0 B1 B2 B3 B4 ª«¬­®¯°±²³´ + # + # any integer padding is likely to be more confusing than this + # fairly compact representation. + # + # However, some might like to see the results with the raw data, + # -Raw exists to provide that behavior: + # PS# 170..180 | format-hex -Raw + # + # Path: + # + # 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F + # + # 00000000 AA 00 00 00 AB 00 00 00 AC 00 00 00 AD 00 00 00 ª...«...¬...­... + # 00000010 AE 00 00 00 AF 00 00 00 B0 00 00 00 B1 00 00 00 ®...¯...°...±... + # 00000020 B2 00 00 00 B3 00 00 00 B4 00 00 00 ²...³...´... + # + # this provides a representation of the piped numbers which includes all + # of the bytes which are in an int32 + if ( $Raw ) + { + $inputBytes = [BitConverter]::GetBytes($InputObject) + $null = $inputStreamArray.AddRange($inputBytes) + } + else + { + # first determine whether we can represent this as a byte + $possibleByte = $InputObject -as [byte] + # first determine whether we can represent this as a int16 + $possibleInt16 = $InputObject -as [int16] + if ( $possibleByte -ne $null ) + { + $null = $inputStreamArray.Add($possibleByte) + } + elseif ( $possibleint16 -ne $null ) + { + $inputBytes = [BitConverter]::GetBytes($possibleInt16) + $null = $inputStreamArray.AddRange($inputBytes) + } + else + { + # now int + $inputBytes = [BitConverter]::GetBytes($InputObject) + $null = $inputStreamArray.AddRange($inputBytes) + } + } + } + else + { + # Otherwise, write a non-terminating error message indicating that input object type is not supported. + $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::FormatHexTypeNotSupported -f $InputObject.GetType() + Write-Error -Message $errorMessage -Category ([System.Management.Automation.ErrorCategory]::ParserError) -ErrorId "FormatHexFailureTypeNotSupported" + } + # Handle streaming case here + # during this process we may not have enough characters to create a ByteCollection + # if we do, create as many ByteCollections as necessary, each being 16 bytes in length + if ( $inputStreamArray.Count -ge $bufferSize ) + { + $rowCount = [math]::Floor($inputStreamArray.Count/$bufferSize) + $arrayLength = $bufferSize * $rowCount + for($i = 0; $i -lt $rowCount; $i++) + { + $rowOffset = $i * $bufferSize + ConvertToHexadecimalHelper -inputBytes $inputStreamArray.GetRange($rowOffset, $bufferSize) -path ' ' -offset $offset + $offset += $bufferSize + } + # We use RemoveRange because of the pathological case of having + # streamed combination of bytes, int16, int32, int64 which are greater + # than 16 bytes. Consider the case: + # $i = [int16]::MaxValue + 3 + # $i64=[int64]::MaxValue -5 + # .{ $i;$i;$i;$i64 } | format-hex + # which create an arraylist 20 bytes + # we need to remove only the bytes from the array which we emitted + $inputStreamArray.RemoveRange(0,$arrayLength) + } + } + } + } + end + { + # now manage any left over bytes in the $inputStreamArray + if ( $PSCmdlet.ParameterSetName -eq "ByInputObject" ) + { + ConvertToHexadecimalHelper $inputStreamArray $null -path ' ' -offset $offset + } + } + +} + +## Imports a PowerShell Data File - a PowerShell hashtable defined in +## a file (such as a Module manifest, session configuration file) +function Import-PowerShellDataFile +{ + [CmdletBinding(DefaultParameterSetName = "ByPath", HelpUri = "http://go.microsoft.com/fwlink/?LinkID=623621")] + [OutputType("System.Collections.Hashtable")] + param( + [Parameter(ParameterSetName = "ByPath", Position = 0)] + [String[]] $Path, + + [Parameter(ParameterSetName = "ByLiteralPath", ValueFromPipelineByPropertyName = $true)] + [Alias("PSPath")] + [String[]] $LiteralPath + ) + + begin + { + function ThrowInvalidDataFile + { + param($resolvedPath, $extraError) + + $errorId = "CouldNotParseAsPowerShellDataFile$extraError" + $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidData + $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::CouldNotParseAsPowerShellDataFile -f $resolvedPath + $exception = [System.InvalidOperationException]::New($errorMessage) + $errorRecord = [System.Management.Automation.ErrorRecord]::New($exception, $errorId, $errorCategory, $null) + $PSCmdlet.WriteError($errorRecord) + } + } + + process + { + foreach($resolvedPath in (Resolve-Path @PSBoundParameters)) + { + $parseErrors = $null + $ast = [System.Management.Automation.Language.Parser]::ParseFile(($resolvedPath.ProviderPath), [ref] $null, [ref] $parseErrors) + if ($parseErrors.Length -gt 0) + { + ThrowInvalidDataFile $resolvedPath + } + else + { + $data = $ast.Find( { $args[0] -is [System.Management.Automation.Language.HashtableAst] }, $false ) + if($data) + { + $data.SafeGetValue() + } + else + { + ThrowInvalidDataFile $resolvedPath "NoHashtableRoot" + } + } + } + } +} + +## Converts a SDDL string into an object-based representation of a security +## descriptor +function ConvertFrom-SddlString +{ + [CmdletBinding(HelpUri = "http://go.microsoft.com/fwlink/?LinkId=623636")] + param( + ## The string representing the security descriptor in SDDL syntax + [Parameter(Mandatory, Position = 0)] + [String] $Sddl, + + ## The type of rights that this SDDL string represents, if any. + [Parameter()] + [ValidateSet( + "FileSystemRights", "RegistryRights", "ActiveDirectoryRights", + "MutexRights", "SemaphoreRights", "CryptoKeyRights", + "EventWaitHandleRights")] + $Type + ) + + ## Translates a SID into a NT Account + function ConvertTo-NtAccount + { + param($Sid) + + if($Sid) + { + $securityIdentifier = [System.Security.Principal.SecurityIdentifier] $Sid + + try + { + $ntAccount = $securityIdentifier.Translate([System.Security.Principal.NTAccount]).ToString() + } + catch{} + + $ntAccount + } + } + + ## Gets the access rights that apply to an access mask, preferring right types + ## of 'Type' if specified. + function Get-AccessRights + { + param($AccessMask, $Type) + + ## All the types of access rights understood by .NET + $rightTypes = [Ordered] @{ + "FileSystemRights" = [System.Security.AccessControl.FileSystemRights] + "RegistryRights" = [System.Security.AccessControl.RegistryRights] + "ActiveDirectoryRights" = [System.DirectoryServices.ActiveDirectoryRights] + "MutexRights" = [System.Security.AccessControl.MutexRights] + "SemaphoreRights" = [System.Security.AccessControl.SemaphoreRights] + "CryptoKeyRights" = [System.Security.AccessControl.CryptoKeyRights] + "EventWaitHandleRights" = [System.Security.AccessControl.EventWaitHandleRights] + } + $typesToExamine = $rightTypes.Values + + ## If they know the access mask represents a certain type, prefer its names + ## (i.e.: CreateLink for the registry over CreateDirectories for the filesystem) + if($Type) + { + $typesToExamine = @($rightTypes[$Type]) + $typesToExamine + } + + + ## Stores the access types we've found that apply + $foundAccess = @() + + ## Store the access types we've already seen, so that we don't report access + ## flags that are essentially duplicate. Many of the access values in the different + ## enumerations have the same value but with different names. + $foundValues = @{} + + ## Go through the entries in the different right types, and see if they apply to the + ## provided access mask. If they do, then add that to the result. + foreach($rightType in $typesToExamine) + { + foreach($accessFlag in [Enum]::GetNames($rightType)) + { + $longKeyValue = [long] $rightType::$accessFlag + if(-not $foundValues.ContainsKey($longKeyValue)) + { + $foundValues[$longKeyValue] = $true + if(($AccessMask -band $longKeyValue) -eq ($longKeyValue)) + { + $foundAccess += $accessFlag + } + } + } + } + + $foundAccess | Sort-Object + } + + ## Converts an ACE into a string representation + function ConvertTo-AceString + { + param( + [Parameter(ValueFromPipeline)] + $Ace, + $Type + ) + + process + { + foreach($aceEntry in $Ace) + { + $AceString = (ConvertTo-NtAccount $aceEntry.SecurityIdentifier) + ": " + $aceEntry.AceQualifier + if($aceEntry.AceFlags -ne "None") + { + $AceString += " " + $aceEntry.AceFlags + } + + if($aceEntry.AccessMask) + { + $foundAccess = Get-AccessRights $aceEntry.AccessMask $Type + + if($foundAccess) + { + $AceString += " ({0})" -f ($foundAccess -join ", ") + } + } + + $AceString + } + } + } + + $rawSecurityDescriptor = [Security.AccessControl.CommonSecurityDescriptor]::new($false,$false,$Sddl) + + $owner = ConvertTo-NtAccount $rawSecurityDescriptor.Owner + $group = ConvertTo-NtAccount $rawSecurityDescriptor.Group + $discretionaryAcl = ConvertTo-AceString $rawSecurityDescriptor.DiscretionaryAcl $Type + $systemAcl = ConvertTo-AceString $rawSecurityDescriptor.SystemAcl $Type + + [PSCustomObject] @{ + Owner = $owner + Group = $group + DiscretionaryAcl = @($discretionaryAcl) + SystemAcl = @($systemAcl) + RawDescriptor = $rawSecurityDescriptor + } +} diff --git a/src/Modules/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 b/src/Modules/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 new file mode 100644 index 0000000000..972d368861 --- /dev/null +++ b/src/Modules/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 @@ -0,0 +1,15 @@ +@{ +GUID="766204A6-330E-4263-A7AB-46C87AFC366C" +Author="Microsoft Corporation" +CompanyName="Microsoft Corporation" +Copyright="© Microsoft Corporation. All rights reserved." +ModuleVersion="3.0.0.0" +PowerShellVersion="3.0" +CLRVersion="4.0" +AliasesToExport = @() +FunctionsToExport = @() +CmdletsToExport="Disable-WSManCredSSP", "Enable-WSManCredSSP", "Get-WSManCredSSP", "Set-WSManQuickConfig", "Test-WSMan", "Invoke-WSManAction", "Connect-WSMan", "Disconnect-WSMan", "Get-WSManInstance", "Set-WSManInstance", "Remove-WSManInstance", "New-WSManInstance", "New-WSManSessionOption" +NestedModules="Microsoft.WSMan.Management.dll" +FormatsToProcess="WSMan.format.ps1xml" +HelpInfoURI = 'http://go.microsoft.com/fwlink/?linkid=390788' +} diff --git a/src/Modules/PSDiagnostics/PSDiagnostics.psd1 b/src/Modules/PSDiagnostics/PSDiagnostics.psd1 new file mode 100644 index 0000000000..3d73330c0a Binary files /dev/null and b/src/Modules/PSDiagnostics/PSDiagnostics.psd1 differ diff --git a/src/Modules/PSDiagnostics/PSDiagnostics.psm1 b/src/Modules/PSDiagnostics/PSDiagnostics.psm1 new file mode 100644 index 0000000000..d36168f27d Binary files /dev/null and b/src/Modules/PSDiagnostics/PSDiagnostics.psm1 differ diff --git a/src/Modules/PSGet/PSGet.Format.ps1xml b/src/Modules/PSGet/PSGet.Format.ps1xml new file mode 100644 index 0000000000..54ff00a1fe --- /dev/null +++ b/src/Modules/PSGet/PSGet.Format.ps1xml @@ -0,0 +1,202 @@ + + + + + PSRepositoryItemInfo + + Microsoft.PowerShell.Commands.PSRepositoryItemInfo + + + + + 10 + + + 35 + + + 10 + + + 20 + + + + + + + + Version + + + Name + + + Type + + + Repository + + + Description + + + + + + + + PSRepository + + Microsoft.PowerShell.Commands.PSRepository + + + + + 25 + + + 25 + + + 20 + + + + + + + + Name + + + PackageManagementProvider + + + InstallationPolicy + + + SourceLocation + + + + + + + + PSScriptInfo + + Microsoft.PowerShell.Commands.PSScriptInfo + + + + + 10 + + + 25 + + + 20 + + + + + + + + Version + + + Name + + + Author + + + Description + + + + + + + + PSGetDscResourceInfo + + Microsoft.PowerShell.Commands.PSGetCommandInfo + Microsoft.PowerShell.Commands.PSGetDscResourceInfo + + + + + 35 + + + 10 + + + 35 + + + + + + + + Name + + + Version + + + ModuleName + + + Repository + + + + + + + + PSGetRoleCapabilityInfo + + Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo + + + + + 35 + + + 10 + + + 35 + + + + + + + + Name + + + Version + + + ModuleName + + + Repository + + + + + + + + diff --git a/src/Modules/PSGet/PSGet.Resource.psd1 b/src/Modules/PSGet/PSGet.Resource.psd1 new file mode 100644 index 0000000000..5177bec6b8 Binary files /dev/null and b/src/Modules/PSGet/PSGet.Resource.psd1 differ diff --git a/src/Modules/PSGet/PSGet.psd1 b/src/Modules/PSGet/PSGet.psd1 new file mode 100644 index 0000000000..9fa416ac98 Binary files /dev/null and b/src/Modules/PSGet/PSGet.psd1 differ diff --git a/src/Modules/PSGet/PSModule.psm1 b/src/Modules/PSGet/PSModule.psm1 new file mode 100644 index 0000000000..6ddcc4db7e --- /dev/null +++ b/src/Modules/PSGet/PSModule.psm1 @@ -0,0 +1,13064 @@ + +######################################################################################### +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# PowerShellGet Module +# +######################################################################################### + +Microsoft.PowerShell.Core\Set-StrictMode -Version Latest + +#region script variables + +# Check if this is nano server. [System.Runtime.Loader.AssemblyLoadContext] is only available on NanoServer +$script:isNanoServer = $null -ne ('System.Runtime.Loader.AssemblyLoadContext' -as [Type]) + +try +{ + $script:MyDocumentsFolderPath = [Environment]::GetFolderPath("MyDocuments") +} +catch +{ + $script:MyDocumentsFolderPath = $null +} + +$script:ProgramFilesPSPath = Microsoft.PowerShell.Management\Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell" + +$script:MyDocumentsPSPath = if($script:MyDocumentsFolderPath) + { + Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsFolderPath -ChildPath "WindowsPowerShell" + } + else + { + Microsoft.PowerShell.Management\Join-Path -Path $env:USERPROFILE -ChildPath "Documents\WindowsPowerShell" + } + +$script:ProgramFilesModulesPath = Microsoft.PowerShell.Management\Join-Path -Path $script:ProgramFilesPSPath -ChildPath "Modules" +$script:MyDocumentsModulesPath = Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsPSPath -ChildPath "Modules" + +$script:ProgramFilesScriptsPath = Microsoft.PowerShell.Management\Join-Path -Path $script:ProgramFilesPSPath -ChildPath "Scripts" + +$script:MyDocumentsScriptsPath = Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsPSPath -ChildPath "Scripts" + +$script:TempPath = ([System.IO.DirectoryInfo]$env:TEMP).FullName +$script:PSGetItemInfoFileName = "PSGetModuleInfo.xml" +$script:PSGetProgramDataPath = Microsoft.PowerShell.Management\Join-Path -Path $env:ProgramData -ChildPath 'Microsoft\Windows\PowerShell\PowerShellGet\' +$script:PSGetAppLocalPath = Microsoft.PowerShell.Management\Join-Path -Path $env:LOCALAPPDATA -ChildPath 'Microsoft\Windows\PowerShell\PowerShellGet\' +$script:PSGetModuleSourcesFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:PSGetAppLocalPath -ChildPath "PSRepositories.xml" +$script:PSGetModuleSources = $null +$script:PSGetInstalledModules = $null +$script:PSGetSettingsFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:PSGetAppLocalPath -ChildPath "PowerShellGetSettings.xml" +$script:PSGetSettings = $null + +$script:MyDocumentsInstalledScriptInfosPath = Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsScriptsPath -ChildPath 'InstalledScriptInfos' +$script:ProgramFilesInstalledScriptInfosPath = Microsoft.PowerShell.Management\Join-Path -Path $script:ProgramFilesScriptsPath -ChildPath 'InstalledScriptInfos' + +$script:InstalledScriptInfoFileName = 'InstalledScriptInfo.xml' +$script:PSGetInstalledScripts = $null + +# Public PSGallery module source name and location +$Script:PSGalleryModuleSource="PSGallery" +$Script:PSGallerySourceUri = 'https://go.microsoft.com/fwlink/?LinkID=397631&clcid=0x409' +$Script:PSGalleryPublishUri = 'https://go.microsoft.com/fwlink/?LinkID=397527&clcid=0x409' +$Script:PSGalleryScriptSourceUri = 'https://go.microsoft.com/fwlink/?LinkID=622995&clcid=0x409' + +# PSGallery V3 Source +$Script:PSGalleryV3SourceUri = 'https://go.microsoft.com/fwlink/?LinkId=528403&clcid=0x409' + +$Script:PSGalleryV2ApiAvailable = $true +$Script:PSGalleryV3ApiAvailable = $false +$Script:PSGalleryApiChecked = $false + +$Script:ResponseUri = "ResponseUri" +$Script:StatusCode = "StatusCode" +$Script:Exception = "Exception" + +$script:PSModuleProviderName = 'PowerShellGet' +$script:PackageManagementProviderParam = "PackageManagementProvider" +$script:PublishLocation = "PublishLocation" +$script:ScriptSourceLocation = 'ScriptSourceLocation' +$script:ScriptPublishLocation = 'ScriptPublishLocation' + +$script:NuGetProviderName = "NuGet" +$script:NuGetProviderVersion = [Version]'2.8.5.201' + +$script:SupportsPSModulesFeatureName="supports-powershell-modules" +$script:FastPackRefHastable = @{} +$script:NuGetBinaryProgramDataPath="$env:ProgramFiles\PackageManagement\ProviderAssemblies" +$script:NuGetBinaryLocalAppDataPath="$env:LOCALAPPDATA\PackageManagement\ProviderAssemblies" +# go fwlink for 'https://nuget.org/nuget.exe' +$script:NuGetClientSourceURL = 'http://go.microsoft.com/fwlink/?LinkID=690216&clcid=0x409' +$script:NuGetExeName = 'NuGet.exe' +$script:NuGetExePath = $null +$script:NuGetProvider = $null +# PowerShellGetFormatVersion will be incremented when we change the .nupkg format structure. +# PowerShellGetFormatVersion is in the form of Major.Minor. +# Minor is incremented for the backward compatible format change. +# Major is incremented for the breaking change. +$script:CurrentPSGetFormatVersion = "1.0" +$script:PSGetFormatVersion = "PowerShellGetFormatVersion" +$script:SupportedPSGetFormatVersionMajors = @("1") +$script:ModuleReferences = 'Module References' +$script:AllVersions = "AllVersions" +$script:Filter = "Filter" +$script:IncludeValidSet = @('DscResource','Cmdlet','Function','Workflow','RoleCapability') +$script:DscResource = "PSDscResource" +$script:Command = "PSCommand" +$script:Cmdlet = "PSCmdlet" +$script:Function = "PSFunction" +$script:Workflow = "PSWorkflow" +$script:RoleCapability = 'PSRoleCapability' +$script:Includes = "PSIncludes" +$script:Tag = "Tag" +$script:NotSpecified= '_NotSpecified_' +$script:PSGetModuleName = 'PowerShellGet' +$script:FindByCanonicalId = 'FindByCanonicalId' +$script:InstalledLocation = 'InstalledLocation' +$script:PSArtifactType = 'Type' +$script:PSArtifactTypeModule = 'Module' +$script:PSArtifactTypeScript = 'Script' +$script:All = 'All' + +$script:Name = 'Name' +$script:Version = 'Version' +$script:Guid = 'Guid' +$script:Path = 'Path' +$script:ScriptBase = 'ScriptBase' +$script:Description = 'Description' +$script:Author = 'Author' +$script:CompanyName = 'CompanyName' +$script:Copyright = 'Copyright' +$script:Tags = 'Tags' +$script:LicenseUri = 'LicenseUri' +$script:ProjectUri = 'ProjectUri' +$script:IconUri = 'IconUri' +$script:RequiredModules = 'RequiredModules' +$script:ExternalModuleDependencies = 'ExternalModuleDependencies' +$script:ReleaseNotes = 'ReleaseNotes' +$script:RequiredScripts = 'RequiredScripts' +$script:ExternalScriptDependencies = 'ExternalScriptDependencies' +$script:DefinedCommands = 'DefinedCommands' +$script:DefinedFunctions = 'DefinedFunctions' +$script:DefinedWorkflows = 'DefinedWorkflows' +$script:TextInfo = (Get-Culture).TextInfo + +$script:PSScriptInfoProperties = @($script:Name + $script:Version, + $script:Guid, + $script:Path, + $script:ScriptBase, + $script:Description, + $script:Author, + $script:CompanyName, + $script:Copyright, + $script:Tags, + $script:ReleaseNotes, + $script:RequiredModules, + $script:ExternalModuleDependencies, + $script:RequiredScripts, + $script:ExternalScriptDependencies, + $script:LicenseUri, + $script:ProjectUri, + $script:IconUri, + $script:DefinedCommands, + $script:DefinedFunctions, + $script:DefinedWorkflows + ) + +$script:SystemEnvironmentKey = 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment' +$script:UserEnvironmentKey = 'HKCU:\Environment' +$script:SystemEnvironmentVariableMaximumLength = 1024 +$script:UserEnvironmentVariableMaximumLength = 255 +$script:EnvironmentVariableTarget = @{ Process = 0; User = 1; Machine = 2 } + +# Wildcard pattern matching configuration. +$script:wildcardOptions = [System.Management.Automation.WildcardOptions]::CultureInvariant -bor ` + [System.Management.Automation.WildcardOptions]::IgnoreCase + +$script:DynamicOptionTypeMap = @{ + 0 = [string]; # String + 1 = [string[]]; # StringArray + 2 = [int]; # Int + 3 = [switch]; # Switch + 4 = [string]; # Folder + 5 = [string]; # File + 6 = [string]; # Path + 7 = [Uri]; # Uri + 8 = [SecureString]; #SecureString + } +#endregion script variables + +#region Module message resolvers +$script:PackageManagementMessageResolverScriptBlock = { + param($i, $Message) + return (PackageManagementMessageResolver -MsgId $i, -Message $Message) + } + +$script:PackageManagementSaveModuleMessageResolverScriptBlock = { + param($i, $Message) + $PackageTarget = $LocalizedData.InstallModulewhatIfMessage + $QuerySaveUntrustedPackage = $LocalizedData.QuerySaveUntrustedPackage + + switch ($i) + { + 'ActionInstallPackage' { return "Save-Module" } + 'QueryInstallUntrustedPackage' {return $QuerySaveUntrustedPackage} + 'TargetPackage' { return $PackageTarget } + Default { + $Message = $Message -creplace "Install", "Download" + $Message = $Message -creplace "install", "download" + return (PackageManagementMessageResolver -MsgId $i, -Message $Message) + } + } + } + +$script:PackageManagementInstallModuleMessageResolverScriptBlock = { + param($i, $Message) + $PackageTarget = $LocalizedData.InstallModulewhatIfMessage + + switch ($i) + { + 'ActionInstallPackage' { return "Install-Module" } + 'TargetPackage' { return $PackageTarget } + Default { + return (PackageManagementMessageResolver -MsgId $i, -Message $Message) + } + } + } + +$script:PackageManagementUnInstallModuleMessageResolverScriptBlock = { + param($i, $Message) + $PackageTarget = $LocalizedData.InstallModulewhatIfMessage + switch ($i) + { + 'ActionUninstallPackage' { return "Uninstall-Module" } + 'TargetPackageVersion' { return $PackageTarget } + Default { + return (PackageManagementMessageResolver -MsgId $i, -Message $Message) + } + } + } + +$script:PackageManagementUpdateModuleMessageResolverScriptBlock = { + param($i, $Message) + $PackageTarget = ($LocalizedData.UpdateModulewhatIfMessage -replace "__OLDVERSION__",$($psgetItemInfo.Version)) + switch ($i) + { + 'ActionInstallPackage' { return "Update-Module" } + 'TargetPackage' { return $PackageTarget } + Default { + return (PackageManagementMessageResolver -MsgId $i, -Message $Message) + } + } + } + +function PackageManagementMessageResolver($MsgID, $Message) { + $NoMatchFound = $LocalizedData.NoMatchFound + $SourceNotFound = $LocalizedData.SourceNotFound + $ModuleIsNotTrusted = $LocalizedData.ModuleIsNotTrusted + $RepositoryIsNotTrusted = $LocalizedData.RepositoryIsNotTrusted + $QueryInstallUntrustedPackage = $LocalizedData.QueryInstallUntrustedPackage + + switch ($MsgID) + { + 'NoMatchFound' { return $NoMatchFound } + 'SourceNotFound' { return $SourceNotFound } + 'CaptionPackageNotTrusted' { return $ModuleIsNotTrusted } + 'CaptionSourceNotTrusted' { return $RepositoryIsNotTrusted } + 'QueryInstallUntrustedPackage' {return $QueryInstallUntrustedPackage} + Default { + if($Message) + { + $tempMessage = $Message -creplace "PackageSource", "PSRepository" + $tempMessage = $Message -creplace "packagesource", "psrepository" + $tempMessage = $Message -creplace "Package", "Module" + $tempMessage = $tempMessage -creplace "package", "module" + $tempMessage = $tempMessage -creplace "Sources", "Repositories" + $tempMessage = $tempMessage -creplace "sources", "repositories" + $tempMessage = $tempMessage -creplace "Source", "Repository" + $tempMessage = $tempMessage -creplace "source", "repository" + + return $tempMessage + } + } + } +} + +#endregion Module message resolvers + +#region Script message resolvers +$script:PackageManagementMessageResolverScriptBlockForScriptCmdlets = { + param($i, $Message) + return (PackageManagementMessageResolverForScripts -MsgId $i, -Message $Message) + } + +$script:PackageManagementSaveScriptMessageResolverScriptBlock = { + param($i, $Message) + $PackageTarget = $LocalizedData.InstallScriptwhatIfMessage + $QuerySaveUntrustedPackage = $LocalizedData.QuerySaveUntrustedScriptPackage + + switch ($i) + { + 'ActionInstallPackage' { return "Save-Script" } + 'QueryInstallUntrustedPackage' {return $QuerySaveUntrustedPackage} + 'TargetPackage' { return $PackageTarget } + Default { + $Message = $Message -creplace "Install", "Download" + $Message = $Message -creplace "install", "download" + return (PackageManagementMessageResolverForScripts -MsgId $i, -Message $Message) + } + } + } + +$script:PackageManagementInstallScriptMessageResolverScriptBlock = { + param($i, $Message) + $PackageTarget = $LocalizedData.InstallScriptwhatIfMessage + + switch ($i) + { + 'ActionInstallPackage' { return "Install-Script" } + 'TargetPackage' { return $PackageTarget } + Default { + return (PackageManagementMessageResolverForScripts -MsgId $i, -Message $Message) + } + } + } + +$script:PackageManagementUnInstallScriptMessageResolverScriptBlock = { + param($i, $Message) + $PackageTarget = $LocalizedData.InstallScriptwhatIfMessage + switch ($i) + { + 'ActionUninstallPackage' { return "Uninstall-Script" } + 'TargetPackageVersion' { return $PackageTarget } + Default { + return (PackageManagementMessageResolverForScripts -MsgId $i, -Message $Message) + } + } + } + +$script:PackageManagementUpdateScriptMessageResolverScriptBlock = { + param($i, $Message) + $PackageTarget = ($LocalizedData.UpdateScriptwhatIfMessage -replace "__OLDVERSION__",$($psgetItemInfo.Version)) + switch ($i) + { + 'ActionInstallPackage' { return "Update-Script" } + 'TargetPackage' { return $PackageTarget } + Default { + return (PackageManagementMessageResolverForScripts -MsgId $i, -Message $Message) + } + } + } + +function PackageManagementMessageResolverForScripts($MsgID, $Message) { + $NoMatchFound = $LocalizedData.NoMatchFoundForScriptName + $SourceNotFound = $LocalizedData.SourceNotFound + $ScriptIsNotTrusted = $LocalizedData.ScriptIsNotTrusted + $RepositoryIsNotTrusted = $LocalizedData.RepositoryIsNotTrusted + $QueryInstallUntrustedPackage = $LocalizedData.QueryInstallUntrustedScriptPackage + + switch ($MsgID) + { + 'NoMatchFound' { return $NoMatchFound } + 'SourceNotFound' { return $SourceNotFound } + 'CaptionPackageNotTrusted' { return $ScriptIsNotTrusted } + 'CaptionSourceNotTrusted' { return $RepositoryIsNotTrusted } + 'QueryInstallUntrustedPackage' {return $QueryInstallUntrustedPackage} + Default { + if($Message) + { + $tempMessage = $Message -creplace "PackageSource", "PSRepository" + $tempMessage = $Message -creplace "packagesource", "psrepository" + $tempMessage = $Message -creplace "Package", "Script" + $tempMessage = $tempMessage -creplace "package", "script" + $tempMessage = $tempMessage -creplace "Sources", "Repositories" + $tempMessage = $tempMessage -creplace "sources", "repositories" + $tempMessage = $tempMessage -creplace "Source", "Repository" + $tempMessage = $tempMessage -creplace "source", "repository" + + return $tempMessage + } + } + } +} + +#endregion Script message resolvers + +Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PSGet.Resource.psd1 + +#region Add .Net type for Telemetry APIs + +# This code is required to add a .Net type and call the Telemetry APIs +# This is required since PowerShell does not support generation of .Net Anonymous types +# +$requiredAssembly = ( + "system.management.automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" + ) + +$source = @" +using System; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Get +{ + public static class Telemetry + { + public static void TraceMessageArtifactsNotFound(string[] artifactsNotFound, string operationName) + { + Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceMessage(operationName, new { ArtifactsNotFound = artifactsNotFound }); + } + + public static void TraceMessageNonPSGalleryRegistration(string sourceLocationType, string sourceLocationHash, string installationPolicy, string packageManagementProvider, string publishLocationHash, string scriptSourceLocationHash, string scriptPublishLocationHash, string operationName) + { + Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceMessage(operationName, new { SourceLocationType = sourceLocationType, SourceLocationHash = sourceLocationHash, InstallationPolicy = installationPolicy, PackageManagementProvider = packageManagementProvider, PublishLocationHash = publishLocationHash, ScriptSourceLocationHash = scriptSourceLocationHash, ScriptPublishLocationHash = scriptPublishLocationHash }); + } + + } +} +"@ + +# Telemetry is turned off by default. +$script:TelemetryEnabled = $false + +try +{ + Add-Type -ReferencedAssemblies $requiredAssembly -TypeDefinition $source -Language CSharp -ErrorAction SilentlyContinue + + # If the telemetry namespace/methods are not found flow goes to the catch block where telemetry is disabled + $telemetryMethods = ([Microsoft.PowerShell.Get.Telemetry] | Get-Member -Static).Name + + if ($telemetryMethods.Contains("TraceMessageArtifactsNotFound") -and $telemetryMethods.Contains("TraceMessageNonPSGalleryRegistration")) + { + # Turn ON Telemetry if the infrastructure is present on the machine + $script:TelemetryEnabled = $true + } +} +catch +{ + # Disable Telemetry if there are any issues finding/loading the Telemetry infrastructure + $script:TelemetryEnabled = $false +} + + +#endregion + +#region *-Module cmdlets +function Publish-Module +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(SupportsShouldProcess=$true, + PositionalBinding=$false, + HelpUri='http://go.microsoft.com/fwlink/?LinkID=398575', + DefaultParameterSetName="ModuleNameParameterSet")] + Param + ( + [Parameter(Mandatory=$true, + ParameterSetName="ModuleNameParameterSet", + ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Name, + + [Parameter(Mandatory=$true, + ParameterSetName="ModulePathParameterSet", + ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Path, + + [Parameter(ParameterSetName="ModuleNameParameterSet")] + [ValidateNotNullOrEmpty()] + [Version] + $RequiredVersion, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $NuGetApiKey, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Repository = $Script:PSGalleryModuleSource, + + [Parameter()] + [ValidateSet("1.0")] + [Version] + $FormatVersion, + + [Parameter()] + [string[]] + $ReleaseNotes, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Tags, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $LicenseUri, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $IconUri, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $ProjectUri + ) + + Begin + { + if($script:isNanoServer) { + $message = $LocalizedData.PublishPSArtifactUnsupportedOnNano -f "Module" + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "PublishModuleIsNotSupportedOnNanoServer" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $PSCmdlet ` + -ErrorCategory InvalidOperation + } + + Get-PSGalleryApiAvailability -Repository $Repository + + if($LicenseUri -and -not (Test-WebUri -uri $LicenseUri)) + { + $message = $LocalizedData.InvalidWebUri -f ($LicenseUri, "LicenseUri") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $LicenseUri + } + + if($IconUri -and -not (Test-WebUri -uri $IconUri)) + { + $message = $LocalizedData.InvalidWebUri -f ($IconUri, "IconUri") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $IconUri + } + + if($ProjectUri -and -not (Test-WebUri -uri $ProjectUri)) + { + $message = $LocalizedData.InvalidWebUri -f ($ProjectUri, "ProjectUri") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $ProjectUri + } + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -BootstrapNuGetExe + } + + Process + { + if($Repository -eq $Script:PSGalleryModuleSource) + { + $moduleSource = Get-PSRepository -Name $Repository -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + if(-not $moduleSource) + { + $message = $LocalizedData.PSGalleryNotFound -f ($Repository) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'PSGalleryNotFound' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Repository + return + } + } + else + { + $ev = $null + $moduleSource = Get-PSRepository -Name $Repository -ErrorVariable ev + if($ev) { return } + } + + $DestinationLocation = $moduleSource.PublishLocation + + if(-not $DestinationLocation -or + (-not (Microsoft.PowerShell.Management\Test-Path $DestinationLocation) -and + -not (Test-WebUri -uri $DestinationLocation))) + + { + $message = $LocalizedData.PSGalleryPublishLocationIsMissing -f ($Repository, $Repository) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PSGalleryPublishLocationIsMissing" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Repository + } + + $message = $LocalizedData.PublishLocation -f ($DestinationLocation) + Write-Verbose -Message $message + + if(-not $NuGetApiKey.Trim()) + { + if(Microsoft.PowerShell.Management\Test-Path -Path $DestinationLocation) + { + $NuGetApiKey = "$(Get-Random)" + } + else + { + $message = $LocalizedData.NuGetApiKeyIsRequiredForNuGetBasedGalleryService -f ($Repository, $DestinationLocation) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "NuGetApiKeyIsRequiredForNuGetBasedGalleryService" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + } + + $providerName = Get-ProviderName -PSCustomObject $moduleSource + if($providerName -ne $script:NuGetProviderName) + { + $message = $LocalizedData.PublishModuleSupportsOnlyNuGetBasedPublishLocations -f ($moduleSource.PublishLocation, $Repository, $Repository) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PublishModuleSupportsOnlyNuGetBasedPublishLocations" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Repository + } + + $moduleName = $null + + if($Name) + { + $module = Microsoft.PowerShell.Core\Get-Module -ListAvailable -Name $Name -Verbose:$false | + Microsoft.PowerShell.Core\Where-Object {-not $RequiredVersion -or ($RequiredVersion -eq $_.Version)} + + if(-not $module) + { + if($RequiredVersion) + { + $message = $LocalizedData.ModuleWithRequiredVersionNotAvailableLocally -f ($Name, $RequiredVersion) + } + else + { + $message = $LocalizedData.ModuleNotAvailableLocally -f ($Name) + } + + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "ModuleNotAvailableLocallyToPublish" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Name + + } + elseif($module.GetType().ToString() -ne "System.Management.Automation.PSModuleInfo") + { + $message = $LocalizedData.AmbiguousModuleName -f ($Name) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "AmbiguousModuleNameToPublish" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Name + } + + $moduleName = $module.Name + $Path = $module.ModuleBase + } + else + { + $resolvedPath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $resolvedPath -or + -not (Microsoft.PowerShell.Management\Test-Path -Path $resolvedPath -PathType Container)) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage ($LocalizedData.PathIsNotADirectory -f ($Path)) ` + -ErrorId "PathIsNotADirectory" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Path + return + } + + $moduleName = Microsoft.PowerShell.Management\Split-Path -Path $resolvedPath -Leaf + $modulePathWithVersion = $false + + # if the Leaf of the $resolvedPath is a version, use its parent folder name as the module name + $ModuleVersion = New-Object System.Version + if([System.Version]::TryParse($moduleName, ([ref]$ModuleVersion))) + { + $moduleName = Microsoft.PowerShell.Management\Split-Path -Path (Microsoft.PowerShell.Management\Split-Path $resolvedPath -Parent) -Leaf + $modulePathWithVersion = $true + } + + $manifestPath = Join-Path -Path $resolvedPath -ChildPath "$moduleName.psd1" + $module = $null + + if(Microsoft.PowerShell.Management\Test-Path -Path $manifestPath -PathType Leaf) + { + $ev = $null + $module = Microsoft.PowerShell.Core\Test-ModuleManifest -Path $manifestPath ` + -ErrorVariable ev ` + -Verbose:$VerbosePreference + if($ev) + { + # Above Test-ModuleManifest cmdlet should write an errors to the Errors stream and Console. + return + } + } + elseif(-not $modulePathWithVersion -and ($PSVersionTable.PSVersion -ge [Version]'5.0')) + { + $module = Microsoft.PowerShell.Core\Get-Module -Name $resolvedPath -ListAvailable -ErrorAction SilentlyContinue -Verbose:$false + } + + if(-not $module) + { + $message = $LocalizedData.InvalidModulePathToPublish -f ($Path) + + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'InvalidModulePathToPublish' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Path + } + elseif($module.GetType().ToString() -ne "System.Management.Automation.PSModuleInfo") + { + $message = $LocalizedData.AmbiguousModulePath -f ($Path) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'AmbiguousModulePathToPublish' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Path + } + + if($module -and (-not $module.Path.EndsWith('.psd1', [System.StringComparison]::OrdinalIgnoreCase))) + { + $message = $LocalizedData.InvalidModuleToPublish -f ($module.Name) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidModuleToPublish" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $module.Name + } + + $moduleName = $module.Name + $Path = $module.ModuleBase + } + + $message = $LocalizedData.PublishModuleLocation -f ($moduleName, $Path) + Write-Verbose -Message $message + + #If users are providing tags using -Tags while running PS 5.0, will show warning messages + if($Tags) + { + $message = $LocalizedData.TagsShouldBeIncludedInManifestFile -f ($moduleName, $Path) + Write-Warning $message + } + + if($ReleaseNotes) + { + $message = $LocalizedData.ReleaseNotesShouldBeIncludedInManifestFile -f ($moduleName, $Path) + Write-Warning $message + } + + if($LicenseUri) + { + $message = $LocalizedData.LicenseUriShouldBeIncludedInManifestFile -f ($moduleName, $Path) + Write-Warning $message + } + + if($IconUri) + { + $message = $LocalizedData.IconUriShouldBeIncludedInManifestFile -f ($moduleName, $Path) + Write-Warning $message + } + + if($ProjectUri) + { + $message = $LocalizedData.ProjectUriShouldBeIncludedInManifestFile -f ($moduleName, $Path) + Write-Warning $message + } + + + # Copy the source module to temp location to publish + $tempModulePath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempPath ` + -ChildPath "$(Microsoft.PowerShell.Utility\Get-Random)\$moduleName" + + if(-not $FormatVersion) + { + $tempModulePathForFormatVersion = $tempModulePath + } + elseif ($FormatVersion -eq "1.0") + { + $tempModulePathForFormatVersion = Microsoft.PowerShell.Management\Join-Path $tempModulePath "Content\Deployment\$script:ModuleReferences\$moduleName" + } + + $null = Microsoft.PowerShell.Management\New-Item -Path $tempModulePathForFormatVersion -ItemType Directory -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + Microsoft.PowerShell.Management\Copy-Item -Path "$Path\*" -Destination $tempModulePathForFormatVersion -Force -Recurse -Confirm:$false -WhatIf:$false + + try + { + $manifestPath = Microsoft.PowerShell.Management\Join-Path $tempModulePathForFormatVersion "$moduleName.psd1" + + if(-not (Microsoft.PowerShell.Management\Test-Path $manifestPath)) + { + $message = $LocalizedData.InvalidModuleToPublish -f ($moduleName) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidModuleToPublish" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $moduleName + } + + $ev = $null + $moduleInfo = Microsoft.PowerShell.Core\Test-ModuleManifest -Path $manifestPath ` + -ErrorVariable ev ` + -Verbose:$VerbosePreference + if($ev) + { + # Above Test-ModuleManifest cmdlet should write an errors to the Errors stream and Console. + return + } + + if(-not $moduleInfo -or + -not $moduleInfo.Author -or + -not $moduleInfo.Description) + { + $message = $LocalizedData.MissingRequiredManifestKeys -f ($moduleName) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "MissingRequiredModuleManifestKeys" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $moduleName + } + + # Check if the specified module name is already used for a script on the specified repository + # Use Find-Script to check if that name is already used as scriptname + $scriptPSGetItemInfo = Find-Script -Name $moduleName ` + -Repository $Repository ` + -Tag 'PSScript' ` + -Verbose:$VerbosePreference ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Debug:$DebugPreference | + Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $moduleName} | + Microsoft.PowerShell.Utility\Select-Object -Last 1 + if($scriptPSGetItemInfo) + { + $message = $LocalizedData.SpecifiedNameIsAlearyUsed -f ($moduleName, $Repository, 'Find-Script') + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "SpecifiedNameIsAlearyUsed" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $moduleName + } + + $currentPSGetItemInfo = Find-Module -Name $moduleInfo.Name ` + -Repository $Repository ` + -Verbose:$VerbosePreference ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Debug:$DebugPreference | + Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $moduleInfo.Name} | + Microsoft.PowerShell.Utility\Select-Object -Last 1 + + if($currentPSGetItemInfo -and $currentPSGetItemInfo.Version -ge $moduleInfo.Version) + { + $message = $LocalizedData.ModuleVersionShouldBeGreaterThanGalleryVersion -f ($moduleInfo.Name, $moduleInfo.Version, $currentPSGetItemInfo.Version, $currentPSGetItemInfo.RepositorySourceLocation) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "ModuleVersionShouldBeGreaterThanGalleryVersion" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + } + + $shouldProcessMessage = $LocalizedData.PublishModulewhatIfMessage -f ($moduleInfo.Version, $moduleInfo.Name) + if($PSCmdlet.ShouldProcess($shouldProcessMessage, "Publish-Module")) + { + Publish-PSArtifactUtility -PSModuleInfo $moduleInfo ` + -ManifestPath $manifestPath ` + -NugetApiKey $NuGetApiKey ` + -Destination $DestinationLocation ` + -Repository $Repository ` + -NugetPackageRoot $tempModulePath ` + -FormatVersion $FormatVersion ` + -ReleaseNotes $($ReleaseNotes -join "`r`n") ` + -Tags $Tags ` + -LicenseUri $LicenseUri ` + -IconUri $IconUri ` + -ProjectUri $ProjectUri ` + -Verbose:$VerbosePreference ` + -WarningAction $WarningPreference ` + -ErrorAction $ErrorActionPreference ` + -Debug:$DebugPreference + } + } + finally + { + Microsoft.PowerShell.Management\Remove-Item $tempModulePath -Force -Recurse -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } + } +} + +function Find-Module +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=398574')] + [outputtype("PSCustomObject[]")] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true, + Position=0)] + [ValidateNotNullOrEmpty()] + [string[]] + $Name, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter()] + [switch] + $AllVersions, + + [Parameter()] + [switch] + $IncludeDependencies, + + [Parameter()] + [ValidateNotNull()] + [string] + $Filter, + + [Parameter()] + [ValidateNotNull()] + [string[]] + $Tag, + + [Parameter()] + [ValidateNotNull()] + [ValidateSet('DscResource','Cmdlet','Function','RoleCapability')] + [string[]] + $Includes, + + [Parameter()] + [ValidateNotNull()] + [string[]] + $DscResource, + + [Parameter()] + [ValidateNotNull()] + [string[]] + $RoleCapability, + + [Parameter()] + [ValidateNotNull()] + [string[]] + $Command, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Repository + ) + + Begin + { + Get-PSGalleryApiAvailability -Repository $Repository + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + } + + Process + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion ` + -AllVersions:$AllVersions + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeModule + + if($PSBoundParameters.ContainsKey("Repository")) + { + $PSBoundParameters["Source"] = $Repository + $null = $PSBoundParameters.Remove("Repository") + + $ev = $null + $null = Get-PSRepository -Name $Repository -ErrorVariable ev -verbose:$false + if($ev) { return } + } + + $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock + + $modulesFoundInPSGallery = @() + + # No Telemetry must be performed if PSGallery is not in the supplied list of Repositories + $isRepositoryNullOrPSGallerySpecified = $false + if ($Repository -and ($Repository -Contains $Script:PSGalleryModuleSource)) + { + $isRepositoryNullOrPSGallerySpecified = $true + } + elseif(-not $Repository) + { + $psgalleryRepo = Get-PSRepository -Name $Script:PSGalleryModuleSource ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + if($psgalleryRepo) + { + $isRepositoryNullOrPSGallerySpecified = $true + } + } + + PackageManagement\Find-Package @PSBoundParameters | Microsoft.PowerShell.Core\ForEach-Object { + + $psgetItemInfo = New-PSGetItemInfo -SoftwareIdentity $_ -Type $script:PSArtifactTypeModule + + $psgetItemInfo + + if ($psgetItemInfo -and + $isRepositoryNullOrPSGallerySpecified -and + $script:TelemetryEnabled -and + ($psgetItemInfo.Repository -eq $Script:PSGalleryModuleSource)) + { + $modulesFoundInPSGallery += $psgetItemInfo.Name + } + } + + # Perform Telemetry if Repository is not supplied or Repository contains PSGallery + # We are only interested in finding modules not in PSGallery + if ($isRepositoryNullOrPSGallerySpecified) + { + Log-ArtifactNotFoundInPSGallery -SearchedName $Name -FoundName $modulesFoundInPSGallery -operationName 'PSGET_FIND_MODULE' + } + } +} + +function Save-Module +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(DefaultParameterSetName='NameAndPathParameterSet', + HelpUri='http://go.microsoft.com/fwlink/?LinkId=531351', + SupportsShouldProcess=$true)] + Param + ( + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNullOrEmpty()] + [string[]] + $Name, + + [Parameter(Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='InputOjectAndPathParameterSet')] + [Parameter(Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='InputOjectAndLiteralPathParameterSet')] + [ValidateNotNull()] + [PSCustomObject[]] + $InputObject, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNullOrEmpty()] + [string[]] + $Repository, + + [Parameter(Mandatory=$true, ParameterSetName='NameAndPathParameterSet')] + [Parameter(Mandatory=$true, ParameterSetName='InputOjectAndPathParameterSet')] + [string] + $Path, + + [Parameter(Mandatory=$true, ParameterSetName='NameAndLiteralPathParameterSet')] + [Parameter(Mandatory=$true, ParameterSetName='InputOjectAndLiteralPathParameterSet')] + [string] + $LiteralPath, + + [Parameter()] + [switch] + $Force + ) + + Begin + { + Get-PSGalleryApiAvailability -Repository $Repository + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + + # Module names already tried in the current pipeline for InputObject parameterset + $moduleNamesInPipeline = @() + } + + Process + { + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementSaveModuleMessageResolverScriptBlock + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeModule + + # When -Force is specified, Path will be created if not available. + if(-not $Force) + { + if($Path) + { + $destinationPath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $destinationPath -or -not (Microsoft.PowerShell.Management\Test-path $destinationPath)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $Path) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $Path ` + -ErrorCategory InvalidArgument + } + + $PSBoundParameters['Path'] = $destinationPath + } + else + { + $destinationPath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $destinationPath -or -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $destinationPath)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $LiteralPath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $LiteralPath ` + -ErrorCategory InvalidArgument + } + + $PSBoundParameters['LiteralPath'] = $destinationPath + } + } + + if($Name) + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -TestWildcardsInName ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + if($PSBoundParameters.ContainsKey("Repository")) + { + $PSBoundParameters["Source"] = $Repository + $null = $PSBoundParameters.Remove("Repository") + + $ev = $null + $null = Get-PSRepository -Name $Repository -ErrorVariable ev -verbose:$false + if($ev) { return } + } + + $null = PackageManagement\Save-Package @PSBoundParameters + } + elseif($InputObject) + { + $null = $PSBoundParameters.Remove("InputObject") + + foreach($inputValue in $InputObject) + { + if (($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSRepositoryItemInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSRepositoryItemInfo") -and + ($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSGetCommandInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSGetCommandInfo") -and + ($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSGetDscResourceInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSGetDscResourceInfo") -and + ($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo")) + + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.InvalidInputObjectValue ` + -ErrorId "InvalidInputObjectValue" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $inputValue + } + + if( ($inputValue.PSTypeNames -contains "Microsoft.PowerShell.Commands.PSGetDscResourceInfo") -or + ($inputValue.PSTypeNames -contains "Deserialized.Microsoft.PowerShell.Commands.PSGetDscResourceInfo") -or + ($inputValue.PSTypeNames -contains "Microsoft.PowerShell.Commands.PSGetCommandInfo") -or + ($inputValue.PSTypeNames -contains "Deserialized.Microsoft.PowerShell.Commands.PSGetCommandInfo") -or + ($inputValue.PSTypeNames -contains "Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo") -or + ($inputValue.PSTypeNames -contains "Deserialized.Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo")) + { + $psgetModuleInfo = $inputValue.PSGetModuleInfo + } + else + { + $psgetModuleInfo = $inputValue + } + + # Skip the module name if it is already tried in the current pipeline + if($moduleNamesInPipeline -contains $psgetModuleInfo.Name) + { + continue + } + + $moduleNamesInPipeline += $psgetModuleInfo.Name + + if ($psgetModuleInfo.PowerShellGetFormatVersion -and + ($script:SupportedPSGetFormatVersionMajors -notcontains $psgetModuleInfo.PowerShellGetFormatVersion.Major)) + { + $message = $LocalizedData.NotSupportedPowerShellGetFormatVersion -f ($psgetModuleInfo.Name, $psgetModuleInfo.PowerShellGetFormatVersion, $psgetModuleInfo.Name) + Write-Error -Message $message -ErrorId "NotSupportedPowerShellGetFormatVersion" -Category InvalidOperation + continue + } + + $PSBoundParameters["Name"] = $psgetModuleInfo.Name + $PSBoundParameters["RequiredVersion"] = $psgetModuleInfo.Version + $PSBoundParameters['Source'] = $psgetModuleInfo.Repository + $PSBoundParameters["PackageManagementProvider"] = (Get-ProviderName -PSCustomObject $psgetModuleInfo) + + $null = PackageManagement\Save-Package @PSBoundParameters + } + } + } +} + +function Install-Module +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(DefaultParameterSetName='NameParameterSet', + HelpUri='http://go.microsoft.com/fwlink/?LinkID=398573', + SupportsShouldProcess=$true)] + Param + ( + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='NameParameterSet')] + [ValidateNotNullOrEmpty()] + [string[]] + $Name, + + [Parameter(Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='InputObject')] + [ValidateNotNull()] + [PSCustomObject[]] + $InputObject, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ParameterSetName='NameParameterSet')] + [ValidateNotNullOrEmpty()] + [string[]] + $Repository, + + [Parameter()] + [ValidateSet("CurrentUser","AllUsers")] + [string] + $Scope = "AllUsers", + + [Parameter()] + [switch] + $Force + ) + + Begin + { + Get-PSGalleryApiAvailability -Repository $Repository + + if(-not (Test-RunningAsElevated) -and ($Scope -ne "CurrentUser")) + { + # Throw an error when Install-Module is used as a non-admin user and '-Scope CurrentUser' is not specified + $message = $LocalizedData.InstallModuleNeedsCurrentUserScopeParameterForNonAdminUser -f @($script:programFilesModulesPath, $script:MyDocumentsModulesPath) + + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InstallModuleNeedsCurrentUserScopeParameterForNonAdminUser" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + + # Module names already tried in the current pipeline for InputObject parameterset + $moduleNamesInPipeline = @() + $YesToAll = $false + $NoToAll = $false + $SourceSGrantedTrust = @() + $SourcesDeniedTrust = @() + } + + Process + { + $RepositoryIsNotTrusted = $LocalizedData.RepositoryIsNotTrusted + $QueryInstallUntrustedPackage = $LocalizedData.QueryInstallUntrustedPackage + $PackageTarget = $LocalizedData.InstallModulewhatIfMessage + + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementInstallModuleMessageResolverScriptBlock + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeModule + $PSBoundParameters['Scope'] = $Scope + + if($PSCmdlet.ParameterSetName -eq "NameParameterSet") + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -TestWildcardsInName ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + if($PSBoundParameters.ContainsKey("Repository")) + { + $PSBoundParameters["Source"] = $Repository + $null = $PSBoundParameters.Remove("Repository") + + $ev = $null + $null = Get-PSRepository -Name $Repository -ErrorVariable ev -verbose:$false + if($ev) { return } + } + + $null = PackageManagement\Install-Package @PSBoundParameters + } + elseif($PSCmdlet.ParameterSetName -eq "InputObject") + { + $null = $PSBoundParameters.Remove("InputObject") + + foreach($inputValue in $InputObject) + { + if (($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSRepositoryItemInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSRepositoryItemInfo") -and + ($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSGetCommandInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSGetCommandInfo") -and + ($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSGetDscResourceInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSGetDscResourceInfo") -and + ($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo")) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.InvalidInputObjectValue ` + -ErrorId "InvalidInputObjectValue" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $inputValue + } + + if( ($inputValue.PSTypeNames -contains "Microsoft.PowerShell.Commands.PSGetDscResourceInfo") -or + ($inputValue.PSTypeNames -contains "Deserialized.Microsoft.PowerShell.Commands.PSGetDscResourceInfo") -or + ($inputValue.PSTypeNames -contains "Microsoft.PowerShell.Commands.PSGetCommandInfo") -or + ($inputValue.PSTypeNames -contains "Deserialized.Microsoft.PowerShell.Commands.PSGetCommandInfo") -or + ($inputValue.PSTypeNames -contains "Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo") -or + ($inputValue.PSTypeNames -contains "Deserialized.Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo")) + { + $psgetModuleInfo = $inputValue.PSGetModuleInfo + } + else + { + $psgetModuleInfo = $inputValue + } + + # Skip the module name if it is already tried in the current pipeline + if($moduleNamesInPipeline -contains $psgetModuleInfo.Name) + { + continue + } + + $moduleNamesInPipeline += $psgetModuleInfo.Name + + if ($psgetModuleInfo.PowerShellGetFormatVersion -and + ($script:SupportedPSGetFormatVersionMajors -notcontains $psgetModuleInfo.PowerShellGetFormatVersion.Major)) + { + $message = $LocalizedData.NotSupportedPowerShellGetFormatVersion -f ($psgetModuleInfo.Name, $psgetModuleInfo.PowerShellGetFormatVersion, $psgetModuleInfo.Name) + Write-Error -Message $message -ErrorId "NotSupportedPowerShellGetFormatVersion" -Category InvalidOperation + continue + } + + $PSBoundParameters["Name"] = $psgetModuleInfo.Name + $PSBoundParameters["RequiredVersion"] = $psgetModuleInfo.Version + $PSBoundParameters['Source'] = $psgetModuleInfo.Repository + $PSBoundParameters["PackageManagementProvider"] = (Get-ProviderName -PSCustomObject $psgetModuleInfo) + + #Check if module is already installed + $InstalledModuleInfo = Test-ModuleInstalled -Name $psgetModuleInfo.Name -RequiredVersion $psgetModuleInfo.Version + if(-not $Force -and $InstalledModuleInfo -ne $null) + { + $message = $LocalizedData.ModuleAlreadyInstalledVerbose -f ($InstalledModuleInfo.Version, $InstalledModuleInfo.Name, $InstalledModuleInfo.ModuleBase) + Write-Verbose -Message $message + } + else + { + $source = $psgetModuleInfo.Repository + $installationPolicy = (Get-PSRepository -Name $source).InstallationPolicy + $ShouldProcessMessage = $PackageTarget -f ($psgetModuleInfo.Name, $psgetModuleInfo.Version) + + if($psCmdlet.ShouldProcess($ShouldProcessMessage)) + { + if($installationPolicy.Equals("Untrusted", [StringComparison]::OrdinalIgnoreCase)) + { + if(-not($YesToAll -or $NoToAll -or $SourceSGrantedTrust.Contains($source) -or $sourcesDeniedTrust.Contains($source) -or $Force)) + { + $message = $QueryInstallUntrustedPackage -f ($psgetModuleInfo.Name, $psgetModuleInfo.RepositorySourceLocation) + if($PSVersionTable.PSVersion -ge [Version]"5.0") + { + $sourceTrusted = $psCmdlet.ShouldContinue("$message", "$RepositoryIsNotTrusted",$true, [ref]$YesToAll, [ref]$NoToAll) + } + else + { + $sourceTrusted = $psCmdlet.ShouldContinue("$message", "$RepositoryIsNotTrusted", [ref]$YesToAll, [ref]$NoToAll) + } + + if($sourceTrusted) + { + $SourceSGrantedTrust+=$source + } + else + { + $SourcesDeniedTrust+=$source + } + } + } + + if($installationPolicy.Equals("trusted", [StringComparison]::OrdinalIgnoreCase) -or $SourceSGrantedTrust.Contains($source) -or $YesToAll -or $Force) + { + $PSBoundParameters["Force"] = $true + $null = PackageManagement\Install-Package @PSBoundParameters + } + } + } + } + } + } +} + +function Update-Module +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(SupportsShouldProcess=$true, + HelpUri='http://go.microsoft.com/fwlink/?LinkID=398576')] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true, + Position=0)] + [ValidateNotNullOrEmpty()] + [String[]] + $Name, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter()] + [Switch] + $Force + ) + + Begin + { + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + + # Module names already tried in the current pipeline + $moduleNamesInPipeline = @() + } + + Process + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + $GetPackageParameters = @{} + $GetPackageParameters[$script:PSArtifactType] = $script:PSArtifactTypeModule + $GetPackageParameters["Provider"] = $script:PSModuleProviderName + $GetPackageParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock + $GetPackageParameters['ErrorAction'] = 'SilentlyContinue' + $GetPackageParameters['WarningAction'] = 'SilentlyContinue' + + $PSGetItemInfos = @() + + if($Name) + { + foreach($moduleName in $Name) + { + $GetPackageParameters['Name'] = $moduleName + $installedPackages = PackageManagement\Get-Package @GetPackageParameters + + if(-not $installedPackages -and -not (Test-WildcardPattern -Name $moduleName)) + { + $availableModules = Get-Module -ListAvailable $moduleName -Verbose:$false | Microsoft.PowerShell.Utility\Select-Object -Unique + + if(-not $availableModules) + { + $message = $LocalizedData.ModuleNotInstalledOnThisMachine -f ($moduleName) + Write-Error -Message $message -ErrorId 'ModuleNotInstalledOnThisMachine' -Category InvalidOperation -TargetObject $moduleName + } + else + { + $message = $LocalizedData.ModuleNotInstalledUsingPowerShellGet -f ($moduleName) + Write-Error -Message $message -ErrorId 'ModuleNotInstalledUsingInstallModuleCmdlet' -Category InvalidOperation -TargetObject $moduleName + } + + continue + } + + $installedPackages | + Microsoft.PowerShell.Core\ForEach-Object {New-PSGetItemInfo -SoftwareIdentity $_ -Type $script:PSArtifactTypeModule} | + Microsoft.PowerShell.Core\ForEach-Object { + if(-not (Test-RunningAsElevated) -and $_.InstalledLocation.StartsWith($script:programFilesModulesPath, [System.StringComparison]::OrdinalIgnoreCase)) + { + if(-not (Test-WildcardPattern -Name $moduleName)) + { + $message = $LocalizedData.AdminPrivilegesRequiredForUpdate -f ($_.Name, $_.InstalledLocation) + Write-Error -Message $message -ErrorId "AdminPrivilegesAreRequiredForUpdate" -Category InvalidOperation -TargetObject $moduleName + } + continue + } + + $PSGetItemInfos += $_ + } + } + } + else + { + + $PSGetItemInfos = PackageManagement\Get-Package @GetPackageParameters | + Microsoft.PowerShell.Core\ForEach-Object {New-PSGetItemInfo -SoftwareIdentity $_ -Type $script:PSArtifactTypeModule} | + Microsoft.PowerShell.Core\Where-Object { + (Test-RunningAsElevated) -or + $_.InstalledLocation.StartsWith($script:MyDocumentsModulesPath, [System.StringComparison]::OrdinalIgnoreCase) + } + } + + + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementUpdateModuleMessageResolverScriptBlock + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeModule + + foreach($psgetItemInfo in $PSGetItemInfos) + { + # Skip the module name if it is already tried in the current pipeline + if($moduleNamesInPipeline -contains $psgetItemInfo.Name) + { + continue + } + + $moduleNamesInPipeline += $psgetItemInfo.Name + + $message = $LocalizedData.CheckingForModuleUpdate -f ($psgetItemInfo.Name) + Write-Verbose -Message $message + + $providerName = Get-ProviderName -PSCustomObject $psgetItemInfo + if(-not $providerName) + { + $providerName = $script:NuGetProviderName + } + + $PSBoundParameters["Name"] = $psgetItemInfo.Name + $PSBoundParameters['Source'] = $psgetItemInfo.Repository + + Get-PSGalleryApiAvailability -Repository (Get-SourceName -Location $psgetItemInfo.RepositorySourceLocation) + + $PSBoundParameters["PackageManagementProvider"] = $providerName + $PSBoundParameters["InstallUpdate"] = $true + + if($psgetItemInfo.InstalledLocation.ToString().StartsWith($script:MyDocumentsModulesPath, [System.StringComparison]::OrdinalIgnoreCase)) + { + $PSBoundParameters["Scope"] = "CurrentUser" + } + else + { + $PSBoundParameters['Scope'] = 'AllUsers' + } + + $sid = PackageManagement\Install-Package @PSBoundParameters + } + } +} + +function Uninstall-Module +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(DefaultParameterSetName='NameParameterSet', + SupportsShouldProcess=$true, + HelpUri='http://go.microsoft.com/fwlink/?LinkId=526864')] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true, + Mandatory=$true, + Position=0, + ParameterSetName='NameParameterSet')] + [ValidateNotNullOrEmpty()] + [String[]] + $Name, + + [Parameter(Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='InputObject')] + [ValidateNotNull()] + [PSCustomObject[]] + $InputObject, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter(ParameterSetName='NameParameterSet')] + [switch] + $AllVersions, + + [Parameter()] + [Switch] + $Force + ) + + Process + { + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementUnInstallModuleMessageResolverScriptBlock + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeModule + + if($PSCmdlet.ParameterSetName -eq "InputObject") + { + $null = $PSBoundParameters.Remove("InputObject") + + foreach($inputValue in $InputObject) + { + if (($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSRepositoryItemInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSRepositoryItemInfo")) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.InvalidInputObjectValue ` + -ErrorId "InvalidInputObjectValue" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $inputValue + } + + $PSBoundParameters["Name"] = $inputValue.Name + $PSBoundParameters["RequiredVersion"] = $inputValue.Version + + $null = PackageManagement\Uninstall-Package @PSBoundParameters + } + } + else + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -TestWildcardsInName ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion ` + -AllVersions:$AllVersions + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + $null = PackageManagement\Uninstall-Package @PSBoundParameters + } + } +} + +function Get-InstalledModule +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=526863')] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true, + Position=0)] + [ValidateNotNullOrEmpty()] + [String[]] + $Name, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter()] + [switch] + $AllVersions + ) + + Process + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion ` + -AllVersions:$AllVersions + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeModule + + PackageManagement\Get-Package @PSBoundParameters | Microsoft.PowerShell.Core\ForEach-Object {New-PSGetItemInfo -SoftwareIdentity $_ -Type $script:PSArtifactTypeModule} + } +} + +#endregion *-Module cmdlets + +#region Find-DscResouce cmdlet + +function Find-DscResource +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(HelpUri = 'http://go.microsoft.com/fwlink/?LinkId=517196')] + [outputtype('PSCustomObject[]')] + Param + ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [string[]] + $Name, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $ModuleName, + + [Parameter()] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter()] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter()] + [switch] + $AllVersions, + + [Parameter()] + [ValidateNotNull()] + [string[]] + $Tag, + + [Parameter()] + [ValidateNotNull()] + [string] + $Filter, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Repository + ) + + + Process + { + $PSBoundParameters['Includes'] = 'DscResource' + + if($PSBoundParameters.ContainsKey('Name')) + { + $PSBoundParameters['DscResource'] = $Name + $null = $PSBoundParameters.Remove('Name') + } + + if($PSBoundParameters.ContainsKey('ModuleName')) + { + $PSBoundParameters['Name'] = $ModuleName + $null = $PSBoundParameters.Remove('ModuleName') + } + + PowerShellGet\Find-Module @PSBoundParameters | + Microsoft.PowerShell.Core\ForEach-Object { + $psgetModuleInfo = $_ + $psgetModuleInfo.Includes.DscResource | Microsoft.PowerShell.Core\ForEach-Object { + if($Name -and ($Name -notcontains $_)) + { + return + } + + $psgetDscResourceInfo = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + Name = $_ + Version = $psgetModuleInfo.Version + ModuleName = $psgetModuleInfo.Name + Repository = $psgetModuleInfo.Repository + PSGetModuleInfo = $psgetModuleInfo + }) + + $psgetDscResourceInfo.PSTypeNames.Insert(0, 'Microsoft.PowerShell.Commands.PSGetDscResourceInfo') + $psgetDscResourceInfo + } + } + } +} + +#endregion Find-DscResouce cmdlet + +#region Find-Command cmdlet + +function Find-Command +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(HelpUri = 'http://go.microsoft.com/fwlink/?LinkId=733636')] + [outputtype('PSCustomObject[]')] + Param + ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [string[]] + $Name, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $ModuleName, + + [Parameter()] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter()] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter()] + [switch] + $AllVersions, + + [Parameter()] + [ValidateNotNull()] + [string[]] + $Tag, + + [Parameter()] + [ValidateNotNull()] + [string] + $Filter, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Repository + ) + + + Process + { + if($PSBoundParameters.ContainsKey('Name')) + { + $PSBoundParameters['Command'] = $Name + $null = $PSBoundParameters.Remove('Name') + } + else + { + $PSBoundParameters['Includes'] = @('Cmdlet','Function') + } + + if($PSBoundParameters.ContainsKey('ModuleName')) + { + $PSBoundParameters['Name'] = $ModuleName + $null = $PSBoundParameters.Remove('ModuleName') + } + + PowerShellGet\Find-Module @PSBoundParameters | + Microsoft.PowerShell.Core\ForEach-Object { + $psgetModuleInfo = $_ + $psgetModuleInfo.Includes.Command | Microsoft.PowerShell.Core\ForEach-Object { + if(($_ -eq "*") -or ($Name -and ($Name -notcontains $_))) + { + return + } + + $psgetCommandInfo = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + Name = $_ + Version = $psgetModuleInfo.Version + ModuleName = $psgetModuleInfo.Name + Repository = $psgetModuleInfo.Repository + PSGetModuleInfo = $psgetModuleInfo + }) + + $psgetCommandInfo.PSTypeNames.Insert(0, 'Microsoft.PowerShell.Commands.PSGetCommandInfo') + $psgetCommandInfo + } + } + } +} + +#endregion Find-Command cmdlet + +#region Find-RoleCapability cmdlet + +function Find-RoleCapability +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(HelpUri = 'http://go.microsoft.com/fwlink/?LinkId=718029')] + [outputtype('PSCustomObject[]')] + Param + ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [string[]] + $Name, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $ModuleName, + + [Parameter()] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter()] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter()] + [switch] + $AllVersions, + + [Parameter()] + [ValidateNotNull()] + [string[]] + $Tag, + + [Parameter()] + [ValidateNotNull()] + [string] + $Filter, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Repository + ) + + + Process + { + $PSBoundParameters['Includes'] = 'RoleCapability' + + if($PSBoundParameters.ContainsKey('Name')) + { + $PSBoundParameters['RoleCapability'] = $Name + $null = $PSBoundParameters.Remove('Name') + } + + if($PSBoundParameters.ContainsKey('ModuleName')) + { + $PSBoundParameters['Name'] = $ModuleName + $null = $PSBoundParameters.Remove('ModuleName') + } + + PowerShellGet\Find-Module @PSBoundParameters | + Microsoft.PowerShell.Core\ForEach-Object { + $psgetModuleInfo = $_ + $psgetModuleInfo.Includes.RoleCapability | Microsoft.PowerShell.Core\ForEach-Object { + if($Name -and ($Name -notcontains $_)) + { + return + } + + $psgetRoleCapabilityInfo = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + Name = $_ + Version = $psgetModuleInfo.Version + ModuleName = $psgetModuleInfo.Name + Repository = $psgetModuleInfo.Repository + PSGetModuleInfo = $psgetModuleInfo + }) + + $psgetRoleCapabilityInfo.PSTypeNames.Insert(0, 'Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo') + $psgetRoleCapabilityInfo + } + } + } +} + +#endregion Find-RoleCapability cmdlet + +#region *-Script cmdlets +function Publish-Script +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(SupportsShouldProcess=$true, + PositionalBinding=$false, + DefaultParameterSetName='PathParameterSet', + HelpUri='http://go.microsoft.com/fwlink/?LinkId=619788')] + Param + ( + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + ParameterSetName='PathParameterSet')] + [ValidateNotNullOrEmpty()] + [string] + $Path, + + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + ParameterSetName='LiteralPathParameterSet')] + [ValidateNotNullOrEmpty()] + [string] + $LiteralPath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $NuGetApiKey, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Repository = $Script:PSGalleryModuleSource + ) + + Begin + { + if($script:isNanoServer) { + $message = $LocalizedData.PublishPSArtifactUnsupportedOnNano -f "Script" + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "PublishScriptIsNotSupportedOnNanoServer" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $PSCmdlet ` + -ErrorCategory InvalidOperation + } + + Get-PSGalleryApiAvailability -Repository $Repository + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -BootstrapNuGetExe + } + + Process + { + $scriptFilePath = $null + if($Path) + { + $scriptFilePath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | + Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $scriptFilePath -or + -not (Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $Path) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $Path ` + -ErrorCategory InvalidArgument + } + } + else + { + $scriptFilePath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet | + Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $scriptFilePath -or + -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $scriptFilePath -PathType Leaf)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $LiteralPath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $LiteralPath ` + -ErrorCategory InvalidArgument + } + } + + if(-not $scriptFilePath.EndsWith('.ps1', [System.StringComparison]::OrdinalIgnoreCase)) + { + $errorMessage = ($LocalizedData.InvalidScriptFilePath -f $scriptFilePath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "InvalidScriptFilePath" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $scriptFilePath ` + -ErrorCategory InvalidArgument + return + } + + if($Repository -eq $Script:PSGalleryModuleSource) + { + $repo = Get-PSRepository -Name $Repository -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + if(-not $repo) + { + $message = $LocalizedData.PSGalleryNotFound -f ($Repository) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'PSGalleryNotFound' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Repository + return + } + } + else + { + $ev = $null + $repo = Get-PSRepository -Name $Repository -ErrorVariable ev + if($ev) { return } + } + + $DestinationLocation = $null + + if(Get-Member -InputObject $repo -Name $script:ScriptPublishLocation) + { + $DestinationLocation = $repo.ScriptPublishLocation + } + + if(-not $DestinationLocation -or + (-not (Microsoft.PowerShell.Management\Test-Path -Path $DestinationLocation) -and + -not (Test-WebUri -uri $DestinationLocation))) + + { + $message = $LocalizedData.PSRepositoryScriptPublishLocationIsMissing -f ($Repository, $Repository) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PSRepositoryScriptPublishLocationIsMissing" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Repository + } + + $message = $LocalizedData.PublishLocation -f ($DestinationLocation) + Write-Verbose -Message $message + + if(-not $NuGetApiKey.Trim()) + { + if(Microsoft.PowerShell.Management\Test-Path -Path $DestinationLocation) + { + $NuGetApiKey = "$(Get-Random)" + } + else + { + $message = $LocalizedData.NuGetApiKeyIsRequiredForNuGetBasedGalleryService -f ($Repository, $DestinationLocation) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "NuGetApiKeyIsRequiredForNuGetBasedGalleryService" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + } + + $providerName = Get-ProviderName -PSCustomObject $repo + if($providerName -ne $script:NuGetProviderName) + { + $message = $LocalizedData.PublishScriptSupportsOnlyNuGetBasedPublishLocations -f ($DestinationLocation, $Repository, $Repository) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PublishScriptSupportsOnlyNuGetBasedPublishLocations" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Repository + } + + if($Path) + { + $PSScriptInfo = Test-ScriptFileInfo -Path $scriptFilePath + } + else + { + $PSScriptInfo = Test-ScriptFileInfo -LiteralPath $scriptFilePath + } + + if(-not $PSScriptInfo) + { + # Test-ScriptFileInfo throws the actual error + return + } + + $scriptName = $PSScriptInfo.Name + + # Copy the source script file to temp location to publish + $tempScriptPath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempPath ` + -ChildPath "$(Microsoft.PowerShell.Utility\Get-Random)\$scriptName" + + $null = Microsoft.PowerShell.Management\New-Item -Path $tempScriptPath -ItemType Directory -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + if($Path) + { + Microsoft.PowerShell.Management\Copy-Item -Path $scriptFilePath -Destination $tempScriptPath -Force -Recurse -Confirm:$false -WhatIf:$false + } + else + { + Microsoft.PowerShell.Management\Copy-Item -LiteralPath $scriptFilePath -Destination $tempScriptPath -Force -Recurse -Confirm:$false -WhatIf:$false + } + + try + { + # Check if the specified script name is already used for a module on the specified repository + # Use Find-Module to check if that name is already used as module name + $modulePSGetItemInfo = Find-Module -Name $scriptName ` + -Repository $Repository ` + -Tag 'PSModule' ` + -Verbose:$VerbosePreference ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Debug:$DebugPreference | + Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $scriptName} | + Microsoft.PowerShell.Utility\Select-Object -Last 1 + if($modulePSGetItemInfo) + { + $message = $LocalizedData.SpecifiedNameIsAlearyUsed -f ($scriptName, $Repository, 'Find-Module') + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "SpecifiedNameIsAlearyUsed" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $scriptName + } + + $currentPSGetItemInfo = $null + $currentPSGetItemInfo = Find-Script -Name $scriptName ` + -Repository $Repository ` + -Verbose:$VerbosePreference ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Debug:$DebugPreference | + Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $scriptName} | + Microsoft.PowerShell.Utility\Select-Object -Last 1 + + if($currentPSGetItemInfo -and $currentPSGetItemInfo.Version -ge $PSScriptInfo.Version) + { + $message = $LocalizedData.ScriptVersionShouldBeGreaterThanGalleryVersion -f ($scriptName, + $PSScriptInfo.Version, + $currentPSGetItemInfo.Version, + $currentPSGetItemInfo.RepositorySourceLocation) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "ScriptVersionShouldBeGreaterThanGalleryVersion" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + } + + $shouldProcessMessage = $LocalizedData.PublishScriptwhatIfMessage -f ($PSScriptInfo.Version, $scriptName) + if($PSCmdlet.ShouldProcess($shouldProcessMessage, "Publish-Script")) + { + Publish-PSArtifactUtility -PSScriptInfo $PSScriptInfo ` + -NugetApiKey $NuGetApiKey ` + -Destination $DestinationLocation ` + -Repository $Repository ` + -NugetPackageRoot $tempScriptPath ` + -Verbose:$VerbosePreference ` + -WarningAction $WarningPreference ` + -ErrorAction $ErrorActionPreference ` + -Debug:$DebugPreference + } + } + finally + { + Microsoft.PowerShell.Management\Remove-Item $tempScriptPath -Force -Recurse -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } + } +} + +function Find-Script +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=619785')] + [outputtype("PSCustomObject[]")] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true, + Position=0)] + [ValidateNotNullOrEmpty()] + [string[]] + $Name, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter()] + [switch] + $AllVersions, + + [Parameter()] + [switch] + $IncludeDependencies, + + [Parameter()] + [ValidateNotNull()] + [string] + $Filter, + + [Parameter()] + [ValidateNotNull()] + [string[]] + $Tag, + + [Parameter()] + [ValidateNotNull()] + [ValidateSet('Function','Workflow')] + [string[]] + $Includes, + + [Parameter()] + [ValidateNotNull()] + [string[]] + $Command, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Repository + ) + + Begin + { + Get-PSGalleryApiAvailability -Repository $Repository + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + } + + Process + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion ` + -AllVersions:$AllVersions + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + $PSBoundParameters['Provider'] = $script:PSModuleProviderName + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeScript + + if($PSBoundParameters.ContainsKey("Repository")) + { + $PSBoundParameters["Source"] = $Repository + $null = $PSBoundParameters.Remove("Repository") + + $ev = $null + $repositories = Get-PSRepository -Name $Repository -ErrorVariable ev -verbose:$false + if($ev) { return } + + $RepositoriesWithoutScriptSourceLocation = $false + foreach($repo in $repositories) + { + if(-not $repo.ScriptSourceLocation) + { + $message = $LocalizedData.ScriptSourceLocationIsMissing -f ($repo.Name) + Write-Error -Message $message ` + -ErrorId 'ScriptSourceLocationIsMissing' ` + -Category InvalidArgument ` + -TargetObject $repo.Name ` + -Exception 'System.ArgumentException' + + $RepositoriesWithoutScriptSourceLocation = $true + } + } + + if($RepositoriesWithoutScriptSourceLocation) + { + return + } + } + + $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlockForScriptCmdlets + + $scriptsFoundInPSGallery = @() + + # No Telemetry must be performed if PSGallery is not in the supplied list of Repositories + $isRepositoryNullOrPSGallerySpecified = $false + if ($Repository -and ($Repository -Contains $Script:PSGalleryModuleSource)) + { + $isRepositoryNullOrPSGallerySpecified = $true + } + elseif(-not $Repository) + { + $psgalleryRepo = Get-PSRepository -Name $Script:PSGalleryModuleSource ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + # And check for IsDeafult? + if($psgalleryRepo) + { + $isRepositoryNullOrPSGallerySpecified = $true + } + } + + PackageManagement\Find-Package @PSBoundParameters | Microsoft.PowerShell.Core\ForEach-Object { + $psgetItemInfo = New-PSGetItemInfo -SoftwareIdentity $_ -Type $script:PSArtifactTypeScript + + $psgetItemInfo + + if ($psgetItemInfo -and + $isRepositoryNullOrPSGallerySpecified -and + $script:TelemetryEnabled -and + ($psgetItemInfo.Repository -eq $Script:PSGalleryModuleSource)) + { + $scriptsFoundInPSGallery += $psgetItemInfo.Name + } + } + + # Perform Telemetry if Repository is not supplied or Repository contains PSGallery + # We are only interested in finding artifacts not in PSGallery + if ($isRepositoryNullOrPSGallerySpecified) + { + Log-ArtifactNotFoundInPSGallery -SearchedName $Name -FoundName $scriptsFoundInPSGallery -operationName PSGET_FIND_SCRIPT + } + } +} + +function Save-Script +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(DefaultParameterSetName='NameAndPathParameterSet', + HelpUri='http://go.microsoft.com/fwlink/?LinkId=619786', + SupportsShouldProcess=$true)] + Param + ( + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNullOrEmpty()] + [string[]] + $Name, + + [Parameter(Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='InputOjectAndPathParameterSet')] + [Parameter(Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='InputOjectAndLiteralPathParameterSet')] + [ValidateNotNull()] + [PSCustomObject[]] + $InputObject, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndPathParameterSet')] + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndLiteralPathParameterSet')] + [ValidateNotNullOrEmpty()] + [string[]] + $Repository, + + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndPathParameterSet')] + + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + ParameterSetName='InputOjectAndPathParameterSet')] + [string] + $Path, + + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameAndLiteralPathParameterSet')] + + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + ParameterSetName='InputOjectAndLiteralPathParameterSet')] + [string] + $LiteralPath, + + [Parameter()] + [switch] + $Force + ) + + Begin + { + Get-PSGalleryApiAvailability -Repository $Repository + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + + # Script names already tried in the current pipeline for InputObject parameterset + $scriptNamesInPipeline = @() + } + + Process + { + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementSaveScriptMessageResolverScriptBlock + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeScript + + # When -Force is specified, Path will be created if not available. + if(-not $Force) + { + if($Path) + { + $destinationPath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | + Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $destinationPath -or -not (Microsoft.PowerShell.Management\Test-path $destinationPath)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $Path) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $Path ` + -ErrorCategory InvalidArgument + } + + $PSBoundParameters['Path'] = $destinationPath + } + else + { + $destinationPath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet | + Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $destinationPath -or -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $destinationPath)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $LiteralPath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $LiteralPath ` + -ErrorCategory InvalidArgument + } + + $PSBoundParameters['LiteralPath'] = $destinationPath + } + } + + if($Name) + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -TestWildcardsInName ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + if($PSBoundParameters.ContainsKey("Repository")) + { + $PSBoundParameters["Source"] = $Repository + $null = $PSBoundParameters.Remove("Repository") + + $ev = $null + $repositories = Get-PSRepository -Name $Repository -ErrorVariable ev -verbose:$false + if($ev) { return } + + $RepositoriesWithoutScriptSourceLocation = $false + foreach($repo in $repositories) + { + if(-not $repo.ScriptSourceLocation) + { + $message = $LocalizedData.ScriptSourceLocationIsMissing -f ($repo.Name) + Write-Error -Message $message ` + -ErrorId 'ScriptSourceLocationIsMissing' ` + -Category InvalidArgument ` + -TargetObject $repo.Name ` + -Exception 'System.ArgumentException' + + $RepositoriesWithoutScriptSourceLocation = $true + } + } + + if($RepositoriesWithoutScriptSourceLocation) + { + return + } + } + + $null = PackageManagement\Save-Package @PSBoundParameters + } + elseif($InputObject) + { + $null = $PSBoundParameters.Remove("InputObject") + + foreach($inputValue in $InputObject) + { + if (($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSRepositoryItemInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSRepositoryItemInfo")) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.InvalidInputObjectValue ` + -ErrorId "InvalidInputObjectValue" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $inputValue + } + + $psRepositoryItemInfo = $inputValue + + # Skip the script name if it is already tried in the current pipeline + if($scriptNamesInPipeline -contains $psRepositoryItemInfo.Name) + { + continue + } + + $scriptNamesInPipeline += $psRepositoryItemInfo.Name + + if ($psRepositoryItemInfo.PowerShellGetFormatVersion -and + ($script:SupportedPSGetFormatVersionMajors -notcontains $psRepositoryItemInfo.PowerShellGetFormatVersion.Major)) + { + $message = $LocalizedData.NotSupportedPowerShellGetFormatVersionScripts -f ($psRepositoryItemInfo.Name, $psRepositoryItemInfo.PowerShellGetFormatVersion, $psRepositoryItemInfo.Name) + Write-Error -Message $message -ErrorId "NotSupportedPowerShellGetFormatVersion" -Category InvalidOperation + continue + } + + $PSBoundParameters["Name"] = $psRepositoryItemInfo.Name + $PSBoundParameters["RequiredVersion"] = $psRepositoryItemInfo.Version + $PSBoundParameters['Source'] = $psRepositoryItemInfo.Repository + $PSBoundParameters["PackageManagementProvider"] = (Get-ProviderName -PSCustomObject $psRepositoryItemInfo) + + $null = PackageManagement\Save-Package @PSBoundParameters + } + } + } +} + +function Install-Script +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(DefaultParameterSetName='NameParameterSet', + HelpUri='http://go.microsoft.com/fwlink/?LinkId=619784', + SupportsShouldProcess=$true)] + Param + ( + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='NameParameterSet')] + [ValidateNotNullOrEmpty()] + [string[]] + $Name, + + [Parameter(Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='InputObject')] + [ValidateNotNull()] + [PSCustomObject[]] + $InputObject, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ParameterSetName='NameParameterSet')] + [ValidateNotNullOrEmpty()] + [string[]] + $Repository, + + [Parameter()] + [ValidateSet("CurrentUser","AllUsers")] + [string] + $Scope = 'AllUsers', + + [Parameter()] + [Switch] + $NoPathUpdate, + + [Parameter()] + [switch] + $Force + ) + + Begin + { + Get-PSGalleryApiAvailability -Repository $Repository + + if(-not (Test-RunningAsElevated) -and ($Scope -ne "CurrentUser")) + { + # Throw an error when Install-Script is used as a non-admin user and '-Scope CurrentUser' is not specified + $AdminPreviligeErrorMessage = $LocalizedData.InstallScriptNeedsCurrentUserScopeParameterForNonAdminUser -f @($script:ProgramFilesScriptsPath, $script:MyDocumentsScriptsPath) + $AdminPreviligeErrorId = 'InstallScriptNeedsCurrentUserScopeParameterForNonAdminUser' + + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $AdminPreviligeErrorMessage ` + -ErrorId $AdminPreviligeErrorId ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + + # Check and add the scope path to PATH environment variable + if($Scope -eq 'AllUsers') + { + $scopePath = $script:ProgramFilesScriptsPath + } + else + { + $scopePath = $script:MyDocumentsScriptsPath + } + + ValidateAndSet-PATHVariableIfUserAccepts -Scope $Scope ` + -ScopePath $scopePath ` + -NoPathUpdate:$NoPathUpdate ` + -Force:$Force + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + + # Script names already tried in the current pipeline for InputObject parameterset + $scriptNamesInPipeline = @() + + $YesToAll = $false + $NoToAll = $false + $SourceSGrantedTrust = @() + $SourcesDeniedTrust = @() + } + + Process + { + $RepositoryIsNotTrusted = $LocalizedData.RepositoryIsNotTrusted + $QueryInstallUntrustedPackage = $LocalizedData.QueryInstallUntrustedScriptPackage + $PackageTarget = $LocalizedData.InstallScriptwhatIfMessage + + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementInstallScriptMessageResolverScriptBlock + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeScript + $PSBoundParameters['Scope'] = $Scope + + if($PSCmdlet.ParameterSetName -eq "NameParameterSet") + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -TestWildcardsInName ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + if($PSBoundParameters.ContainsKey("Repository")) + { + $PSBoundParameters["Source"] = $Repository + $null = $PSBoundParameters.Remove("Repository") + + $ev = $null + $repositories = Get-PSRepository -Name $Repository -ErrorVariable ev -verbose:$false + if($ev) { return } + + $RepositoriesWithoutScriptSourceLocation = $false + foreach($repo in $repositories) + { + if(-not $repo.ScriptSourceLocation) + { + $message = $LocalizedData.ScriptSourceLocationIsMissing -f ($repo.Name) + Write-Error -Message $message ` + -ErrorId 'ScriptSourceLocationIsMissing' ` + -Category InvalidArgument ` + -TargetObject $repo.Name ` + -Exception 'System.ArgumentException' + + $RepositoriesWithoutScriptSourceLocation = $true + } + } + + if($RepositoriesWithoutScriptSourceLocation) + { + return + } + } + + if(-not $Force) + { + foreach($scriptName in $Name) + { + # Throw an error if there is a command with the same name and -force is not specified. + $cmd = Microsoft.PowerShell.Core\Get-Command -Name $scriptName ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + if($cmd) + { + # Check if this script was already installed, may be with -Force + $InstalledScriptInfo = Test-ScriptInstalled -Name $scriptName ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + if(-not $InstalledScriptInfo) + { + $message = $LocalizedData.CommandAlreadyAvailable -f ($scriptName) + Write-Error -Message $message -ErrorId CommandAlreadyAvailableWitScriptName -Category InvalidOperation + + # return if only single name is specified + if($scriptName -eq $Name) + { + return + } + } + } + } + } + + $null = PackageManagement\Install-Package @PSBoundParameters + } + elseif($PSCmdlet.ParameterSetName -eq "InputObject") + { + $null = $PSBoundParameters.Remove("InputObject") + + foreach($inputValue in $InputObject) + { + + if (($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSRepositoryItemInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSRepositoryItemInfo")) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.InvalidInputObjectValue ` + -ErrorId "InvalidInputObjectValue" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $inputValue + } + + $psRepositoryItemInfo = $inputValue + + # Skip the script name if it is already tried in the current pipeline + if($scriptNamesInPipeline -contains $psRepositoryItemInfo.Name) + { + continue + } + + $scriptNamesInPipeline += $psRepositoryItemInfo.Name + + if ($psRepositoryItemInfo.PowerShellGetFormatVersion -and + ($script:SupportedPSGetFormatVersionMajors -notcontains $psRepositoryItemInfo.PowerShellGetFormatVersion.Major)) + { + $message = $LocalizedData.NotSupportedPowerShellGetFormatVersionScripts -f ($psRepositoryItemInfo.Name, $psRepositoryItemInfo.PowerShellGetFormatVersion, $psRepositoryItemInfo.Name) + Write-Error -Message $message -ErrorId "NotSupportedPowerShellGetFormatVersion" -Category InvalidOperation + continue + } + + $PSBoundParameters["Name"] = $psRepositoryItemInfo.Name + $PSBoundParameters["RequiredVersion"] = $psRepositoryItemInfo.Version + $PSBoundParameters['Source'] = $psRepositoryItemInfo.Repository + $PSBoundParameters["PackageManagementProvider"] = (Get-ProviderName -PSCustomObject $psRepositoryItemInfo) + + $InstalledScriptInfo = Test-ScriptInstalled -Name $psRepositoryItemInfo.Name + if(-not $Force -and $InstalledScriptInfo) + { + $message = $LocalizedData.ScriptAlreadyInstalledVerbose -f ($InstalledScriptInfo.Version, $InstalledScriptInfo.Name, $InstalledScriptInfo.ScriptBase) + Write-Verbose -Message $message + } + else + { + # Throw an error if there is a command with the same name and -force is not specified. + if(-not $Force) + { + $cmd = Microsoft.PowerShell.Core\Get-Command -Name $psRepositoryItemInfo.Name ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + if($cmd) + { + $message = $LocalizedData.CommandAlreadyAvailable -f ($psRepositoryItemInfo.Name) + Write-Error -Message $message -ErrorId CommandAlreadyAvailableWitScriptName -Category InvalidOperation + + continue + } + } + + $source = $psRepositoryItemInfo.Repository + $installationPolicy = (Get-PSRepository -Name $source).InstallationPolicy + $ShouldProcessMessage = $PackageTarget -f ($psRepositoryItemInfo.Name, $psRepositoryItemInfo.Version) + + if($psCmdlet.ShouldProcess($ShouldProcessMessage)) + { + if($installationPolicy.Equals("Untrusted", [StringComparison]::OrdinalIgnoreCase)) + { + if(-not($YesToAll -or $NoToAll -or $SourceSGrantedTrust.Contains($source) -or $sourcesDeniedTrust.Contains($source) -or $Force)) + { + $message = $QueryInstallUntrustedPackage -f ($psRepositoryItemInfo.Name, $psRepositoryItemInfo.RepositorySourceLocation) + + if($PSVersionTable.PSVersion -ge [Version]"5.0") + { + $sourceTrusted = $psCmdlet.ShouldContinue("$message", "$RepositoryIsNotTrusted",$true, [ref]$YesToAll, [ref]$NoToAll) + } + else + { + $sourceTrusted = $psCmdlet.ShouldContinue("$message", "$RepositoryIsNotTrusted", [ref]$YesToAll, [ref]$NoToAll) + } + + if($sourceTrusted) + { + $SourcesGrantedTrust+=$source + } + else + { + $SourcesDeniedTrust+=$source + } + } + } + } + if($installationPolicy.Equals("trusted", [StringComparison]::OrdinalIgnoreCase) -or $SourcesGrantedTrust.Contains($source) -or $YesToAll -or $Force) + { + $PSBoundParameters["Force"] = $true + $null = PackageManagement\Install-Package @PSBoundParameters + } + } + } + } + } +} + +function Update-Script +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(SupportsShouldProcess=$true, + HelpUri='http://go.microsoft.com/fwlink/?LinkId=619787')] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true, + Position=0)] + [ValidateNotNullOrEmpty()] + [String[]] + $Name, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter()] + [Switch] + $Force + ) + + Begin + { + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + + # Script names already tried in the current pipeline + $scriptNamesInPipeline = @() + } + + Process + { + $scriptFilePathsToUpdate = @() + + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + if($Name) + { + foreach($scriptName in $Name) + { + $availableScriptPaths = Get-AvailableScriptFilePath -Name $scriptName -Verbose:$false + + if(-not $availableScriptPaths -and -not (Test-WildcardPattern -Name $scriptName)) + { + $message = $LocalizedData.ScriptNotInstalledOnThisMachine -f ($scriptName, $script:MyDocumentsScriptsPath, $script:ProgramFilesScriptsPath) + Write-Error -Message $message -ErrorId "ScriptNotInstalledOnThisMachine" -Category InvalidOperation -TargetObject $scriptName + continue + } + + foreach($scriptFilePath in $availableScriptPaths) + { + $installedScriptFilePath = Get-InstalledScriptFilePath -Name ([System.IO.Path]::GetFileNameWithoutExtension($scriptFilePath)) | + Microsoft.PowerShell.Core\Where-Object {$_ -eq $scriptFilePath } + + # Check if this script got installed with PowerShellGet and user has required permissions + if ($installedScriptFilePath) + { + if(-not (Test-RunningAsElevated) -and $installedScriptFilePath.StartsWith($script:ProgramFilesScriptsPath, [System.StringComparison]::OrdinalIgnoreCase)) + { + if(-not (Test-WildcardPattern -Name $scriptName)) + { + $message = $LocalizedData.AdminPrivilegesRequiredForScriptUpdate -f ($scriptName, $installedScriptFilePath) + Write-Error -Message $message -ErrorId "AdminPrivilegesAreRequiredForUpdate" -Category InvalidOperation -TargetObject $scriptName + } + continue + } + + $scriptFilePathsToUpdate += $installedScriptFilePath + } + else + { + if(-not (Test-WildcardPattern -Name $scriptName)) + { + $message = $LocalizedData.ScriptNotInstalledUsingPowerShellGet -f ($scriptName) + Write-Error -Message $message -ErrorId "ScriptNotInstalledUsingPowerShellGet" -Category InvalidOperation -TargetObject $scriptName + } + continue + } + } + } + } + else + { + $isRunningAsElevated = Test-RunningAsElevated + $installedScriptFilePaths = Get-InstalledScriptFilePath + + if($isRunningAsElevated) + { + $scriptFilePathsToUpdate = $installedScriptFilePaths + } + else + { + # Update the scripts installed under + $scriptFilePathsToUpdate = $installedScriptFilePaths | Microsoft.PowerShell.Core\Where-Object { + $_.StartsWith($script:MyDocumentsScriptsPath, [System.StringComparison]::OrdinalIgnoreCase)} + } + } + + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeScript + $PSBoundParameters["MessageResolver"] = $script:PackageManagementUpdateScriptMessageResolverScriptBlock + $PSBoundParameters["InstallUpdate"] = $true + + foreach($scriptFilePath in $scriptFilePathsToUpdate) + { + $scriptName = [System.IO.Path]::GetFileNameWithoutExtension($scriptFilePath) + + $installedScriptInfoFilePath = $null + $installedScriptInfoFileName = "$($scriptName)_$script:InstalledScriptInfoFileName" + + if($scriptFilePath.ToString().StartsWith($script:MyDocumentsScriptsPath, [System.StringComparison]::OrdinalIgnoreCase)) + { + $PSBoundParameters["Scope"] = "CurrentUser" + $installedScriptInfoFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsInstalledScriptInfosPath ` + -ChildPath $installedScriptInfoFileName + } + elseif($scriptFilePath.ToString().StartsWith($script:ProgramFilesScriptsPath, [System.StringComparison]::OrdinalIgnoreCase)) + { + $PSBoundParameters["Scope"] = "AllUsers" + $installedScriptInfoFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:ProgramFilesInstalledScriptInfosPath ` + -ChildPath $installedScriptInfoFileName + + } + + $psgetItemInfo = $null + if($installedScriptInfoFilePath -and (Microsoft.PowerShell.Management\Test-Path -Path $installedScriptInfoFilePath -PathType Leaf)) + { + $psgetItemInfo = DeSerialize-PSObject -Path $installedScriptInfoFilePath + } + + # Skip the script name if it is already tried in the current pipeline + if(-not $psgetItemInfo -or ($scriptNamesInPipeline -contains $psgetItemInfo.Name)) + { + continue + } + + + $scriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $psgetItemInfo.InstalledLocation ` + -ChildPath "$($psgetItemInfo.Name).ps1" + + # Remove the InstalledScriptInfo.xml file if the actual script file was manually uninstalled by the user + if(-not (Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf)) + { + Microsoft.PowerShell.Management\Remove-Item -Path $installedScriptInfoFilePath -Force -ErrorAction SilentlyContinue + + continue + } + + $scriptNamesInPipeline += $psgetItemInfo.Name + + $message = $LocalizedData.CheckingForScriptUpdate -f ($psgetItemInfo.Name) + Write-Verbose -Message $message + + $providerName = Get-ProviderName -PSCustomObject $psgetItemInfo + if(-not $providerName) + { + $providerName = $script:NuGetProviderName + } + + $PSBoundParameters["PackageManagementProvider"] = $providerName + $PSBoundParameters["Name"] = $psgetItemInfo.Name + $PSBoundParameters['Source'] = $psgetItemInfo.Repository + + Get-PSGalleryApiAvailability -Repository (Get-SourceName -Location $psgetItemInfo.RepositorySourceLocation) + + $sid = PackageManagement\Install-Package @PSBoundParameters + } + } +} + +function Uninstall-Script +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(DefaultParameterSetName='NameParameterSet', + SupportsShouldProcess=$true, + HelpUri='http://go.microsoft.com/fwlink/?LinkId=619789')] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true, + Mandatory=$true, + Position=0, + ParameterSetName='NameParameterSet')] + [ValidateNotNullOrEmpty()] + [String[]] + $Name, + + [Parameter(Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + Position=0, + ParameterSetName='InputObject')] + [ValidateNotNull()] + [PSCustomObject[]] + $InputObject, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true, + ParameterSetName='NameParameterSet')] + [ValidateNotNull()] + [Version] + $MaximumVersion, + + [Parameter()] + [Switch] + $Force + ) + + Process + { + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementUnInstallScriptMessageResolverScriptBlock + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeScript + + if($PSCmdlet.ParameterSetName -eq "InputObject") + { + $null = $PSBoundParameters.Remove("InputObject") + + foreach($inputValue in $InputObject) + { + if (($inputValue.PSTypeNames -notcontains "Microsoft.PowerShell.Commands.PSRepositoryItemInfo") -and + ($inputValue.PSTypeNames -notcontains "Deserialized.Microsoft.PowerShell.Commands.PSRepositoryItemInfo")) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.InvalidInputObjectValue ` + -ErrorId "InvalidInputObjectValue" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $inputValue + } + + $PSBoundParameters["Name"] = $inputValue.Name + $PSBoundParameters["RequiredVersion"] = $inputValue.Version + + $null = PackageManagement\Uninstall-Package @PSBoundParameters + } + } + else + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -TestWildcardsInName ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + $null = PackageManagement\Uninstall-Package @PSBoundParameters + } + } +} + +function Get-InstalledScript +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=619790')] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true, + Position=0)] + [ValidateNotNullOrEmpty()] + [String[]] + $Name, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MinimumVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $RequiredVersion, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNull()] + [Version] + $MaximumVersion + ) + + Process + { + $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet ` + -Name $Name ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion ` + -RequiredVersion $RequiredVersion + + if(-not $ValidationResult) + { + # Validate-VersionParameters throws the error. + # returning to avoid further execution when different values are specified for -ErrorAction parameter + return + } + + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlockForScriptCmdlets + $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeScript + + PackageManagement\Get-Package @PSBoundParameters | Microsoft.PowerShell.Core\ForEach-Object {New-PSGetItemInfo -SoftwareIdentity $_ -Type $script:PSArtifactTypeScript} + } +} + +#endregion *-Script cmdlets + +#region *-PSRepository cmdlets + +function Register-PSRepository +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(PositionalBinding=$false, + HelpUri='http://go.microsoft.com/fwlink/?LinkID=517129')] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Name, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $SourceLocation, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $PublishLocation, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $ScriptSourceLocation, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $ScriptPublishLocation, + + [Parameter()] + [ValidateSet('Trusted','Untrusted')] + [string] + $InstallationPolicy = 'Untrusted', + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $PackageManagementProvider + ) + + DynamicParam + { + if (Get-Variable -Name SourceLocation -ErrorAction SilentlyContinue) + { + Set-Variable -Name selctedProviderName -value $null -Scope 1 + + if(Get-Variable -Name PackageManagementProvider -ErrorAction SilentlyContinue) + { + $selctedProviderName = $PackageManagementProvider + $null = Get-DynamicParameters -Location $SourceLocation -PackageManagementProvider ([REF]$selctedProviderName) + } + else + { + $dynamicParameters = Get-DynamicParameters -Location $SourceLocation -PackageManagementProvider ([REF]$selctedProviderName) + Set-Variable -Name PackageManagementProvider -Value $selctedProviderName -Scope 1 + $null = $dynamicParameters + } + } + } + + Begin + { + Get-PSGalleryApiAvailability -Repository $Name + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + + if($PackageManagementProvider) + { + $providers = PackageManagement\Get-PackageProvider | Where-Object { $_.Name -ne $script:PSModuleProviderName -and $_.Features.ContainsKey($script:SupportsPSModulesFeatureName) } + + if (-not $providers -or $providers.Name -notcontains $PackageManagementProvider) + { + $possibleProviderNames = $script:NuGetProviderName + + if($providers) + { + $possibleProviderNames = ($providers.Name -join ',') + } + + $message = $LocalizedData.InvalidPackageManagementProviderValue -f ($PackageManagementProvider, $possibleProviderNames, $script:NuGetProviderName) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidPackageManagementProviderValue" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $PackageManagementProvider + return + } + } + } + + Process + { + # Ping and resolve the specified location + $SourceLocation = Resolve-Location -Location (Get-LocationString -LocationUri $SourceLocation) ` + -LocationParameterName 'SourceLocation' ` + -CallerPSCmdlet $PSCmdlet + if(-not $SourceLocation) + { + # Above Resolve-Location function throws an error when it is not able to resolve a location + return + } + + if($InstallationPolicy -eq "Trusted") + { + $PSBoundParameters.Add("Trusted", $true) + } + + $providerName = $null + + if($PackageManagementProvider) + { + $providerName = $PackageManagementProvider + } + elseif($selctedProviderName) + { + $providerName = $selctedProviderName + } + else + { + $providerName = Get-PackageManagementProviderName -Location $SourceLocation + } + + if($providerName) + { + $PSBoundParameters[$script:PackageManagementProviderParam] = $providerName + } + + if($PublishLocation) + { + $PSBoundParameters[$script:PublishLocation] = Get-LocationString -LocationUri $PublishLocation + } + + if($ScriptPublishLocation) + { + $PSBoundParameters[$script:ScriptPublishLocation] = Get-LocationString -LocationUri $ScriptPublishLocation + } + + if($ScriptSourceLocation) + { + $PSBoundParameters[$script:ScriptSourceLocation] = Get-LocationString -LocationUri $ScriptSourceLocation + } + + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + + $PSBoundParameters["Location"] = Get-LocationString -LocationUri $SourceLocation + $null = $PSBoundParameters.Remove("SourceLocation") + $null = $PSBoundParameters.Remove("InstallationPolicy") + + $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock + + $null = PackageManagement\Register-PackageSource @PSBoundParameters + } +} + +function Set-PSRepository +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(PositionalBinding=$false, + HelpUri='http://go.microsoft.com/fwlink/?LinkID=517128')] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Name, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $SourceLocation, + + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $PublishLocation, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $ScriptSourceLocation, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $ScriptPublishLocation, + + [Parameter()] + [ValidateSet('Trusted','Untrusted')] + [string] + $InstallationPolicy, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $PackageManagementProvider + ) + + DynamicParam + { + if (Get-Variable -Name Name -ErrorAction SilentlyContinue) + { + $moduleSource = Get-PSRepository -Name $Name -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + + if($moduleSource) + { + $providerName = (Get-ProviderName -PSCustomObject $moduleSource) + + $loc = $moduleSource.SourceLocation + + if(Get-Variable -Name SourceLocation -ErrorAction SilentlyContinue) + { + $loc = $SourceLocation + } + + if(Get-Variable -Name PackageManagementProvider -ErrorAction SilentlyContinue) + { + $providerName = $PackageManagementProvider + } + + $null = Get-DynamicParameters -Location $loc -PackageManagementProvider ([REF]$providerName) + } + } + } + + Begin + { + Get-PSGalleryApiAvailability -Repository $Name + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + + if($PackageManagementProvider) + { + $providers = PackageManagement\Get-PackageProvider | Where-Object { $_.Name -ne $script:PSModuleProviderName -and $_.Features.ContainsKey($script:SupportsPSModulesFeatureName) } + + if (-not $providers -or $providers.Name -notcontains $PackageManagementProvider) + { + $possibleProviderNames = $script:NuGetProviderName + + if($providers) + { + $possibleProviderNames = ($providers.Name -join ',') + } + + $message = $LocalizedData.InvalidPackageManagementProviderValue -f ($PackageManagementProvider, $possibleProviderNames, $script:NuGetProviderName) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidPackageManagementProviderValue" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $PackageManagementProvider + return + } + } + } + + Process + { + # Ping and resolve the specified location + if($SourceLocation) + { + # Ping and resolve the specified location + $SourceLocation = Resolve-Location -Location (Get-LocationString -LocationUri $SourceLocation) ` + -LocationParameterName 'SourceLocation' ` + -CallerPSCmdlet $PSCmdlet + if(-not $SourceLocation) + { + # Above Resolve-Location function throws an error when it is not able to resolve a location + return + } + } + + $ModuleSource = Get-PSRepository -Name $Name -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + + if(-not $ModuleSource) + { + $message = $LocalizedData.RepositoryNotFound -f ($Name) + + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "RepositoryNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $Name + } + + if (-not $PackageManagementProvider) + { + $PackageManagementProvider = (Get-ProviderName -PSCustomObject $ModuleSource) + } + + $Trusted = $ModuleSource.Trusted + if($InstallationPolicy) + { + if($InstallationPolicy -eq "Trusted") + { + $Trusted = $true + } + else + { + $Trusted = $false + } + + $null = $PSBoundParameters.Remove("InstallationPolicy") + } + + if($PublishLocation) + { + $PSBoundParameters[$script:PublishLocation] = Get-LocationString -LocationUri $PublishLocation + } + + if($ScriptPublishLocation) + { + $PSBoundParameters[$script:ScriptPublishLocation] = Get-LocationString -LocationUri $ScriptPublishLocation + } + + if($ScriptSourceLocation) + { + $PSBoundParameters[$script:ScriptSourceLocation] = Get-LocationString -LocationUri $ScriptSourceLocation + } + + if($SourceLocation) + { + $PSBoundParameters["NewLocation"] = Get-LocationString -LocationUri $SourceLocation + + $null = $PSBoundParameters.Remove("SourceLocation") + } + + $PSBoundParameters[$script:PackageManagementProviderParam] = $PackageManagementProvider + $PSBoundParameters.Add("Trusted", $Trusted) + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock + + $null = PackageManagement\Set-PackageSource @PSBoundParameters + } +} + +function Unregister-PSRepository +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=517130')] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true, + Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string[]] + $Name + ) + + Begin + { + Get-PSGalleryApiAvailability -Repository $Name + } + + Process + { + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock + + $null = $PSBoundParameters.Remove("Name") + + foreach ($moduleSourceName in $Name) + { + # Check if $moduleSourceName contains any wildcards + if(Test-WildcardPattern $moduleSourceName) + { + $message = $LocalizedData.RepositoryNameContainsWildCards -f ($moduleSourceName) + Write-Error -Message $message -ErrorId "RepositoryNameContainsWildCards" -Category InvalidOperation + continue + } + + $PSBoundParameters["Source"] = $moduleSourceName + + $null = PackageManagement\Unregister-PackageSource @PSBoundParameters + } + } +} + +function Get-PSRepository +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=517127')] + Param + ( + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string[]] + $Name + ) + + Begin + { + Get-PSGalleryApiAvailability -Repository $Name + } + + Process + { + $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock + + if($Name) + { + foreach($sourceName in $Name) + { + $PSBoundParameters["Name"] = $sourceName + + $packageSources = PackageManagement\Get-PackageSource @PSBoundParameters + + $packageSources | Microsoft.PowerShell.Core\ForEach-Object { New-ModuleSourceFromPackageSource -PackageSource $_ } + } + } + else + { + $packageSources = PackageManagement\Get-PackageSource @PSBoundParameters + + $packageSources | Microsoft.PowerShell.Core\ForEach-Object { New-ModuleSourceFromPackageSource -PackageSource $_ } + } + } +} + +#endregion *-PSRepository cmdlets + +#region *-ScriptFileInfo cmdlets + +# Below is the sample PSScriptInfo in a script file. +<#PSScriptInfo + +.VERSION 1.0 + +.GUID 544238e3-1751-4065-9227-be105ff11636 + +.AUTHOR manikb + +.COMPANYNAME Microsoft Corporation + +.COPYRIGHT (c) 2015 Microsoft Corporation. All rights reserved. + +.TAGS Tag1 Tag2 Tag3 + +.LICENSEURI https://contoso.com/License + +.PROJECTURI https://contoso.com/ + +.ICONURI https://contoso.com/Icon + +.EXTERNALMODULEDEPENDENCIES ExternalModule1 + +.REQUIREDSCRIPTS Start-WFContosoServer,Stop-ContosoServerScript + +.EXTERNALSCRIPTDEPENDENCIES Stop-ContosoServerScript + +.RELEASENOTES +contoso script now supports following features +Feature 1 +Feature 2 +Feature 3 +Feature 4 +Feature 5 + +#> + +<# #Requires -Module statements #> + +<# + +.DESCRIPTION + Description goes here. + +#> + + +# +function Test-ScriptFileInfo +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(PositionalBinding=$false, + DefaultParameterSetName='PathParameterSet', + HelpUri='http://go.microsoft.com/fwlink/?LinkId=619791')] + Param + ( + [Parameter(Mandatory=$true, + Position=0, + ValueFromPipelineByPropertyName=$true, + ParameterSetName='PathParameterSet')] + [ValidateNotNullOrEmpty()] + [string] + $Path, + + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + ParameterSetName='LiteralPathParameterSet')] + [ValidateNotNullOrEmpty()] + [string] + $LiteralPath + ) + + Process + { + $scriptFilePath = $null + if($Path) + { + $scriptFilePath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $scriptFilePath -or -not (Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $Path) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $Path ` + -ErrorCategory InvalidArgument + return + } + } + else + { + $scriptFilePath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $scriptFilePath -or -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $scriptFilePath -PathType Leaf)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $LiteralPath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $LiteralPath ` + -ErrorCategory InvalidArgument + return + } + } + + if(-not $scriptFilePath.EndsWith('.ps1', [System.StringComparison]::OrdinalIgnoreCase)) + { + $errorMessage = ($LocalizedData.InvalidScriptFilePath -f $scriptFilePath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "InvalidScriptFilePath" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $scriptFilePath ` + -ErrorCategory InvalidArgument + return + } + + $PSScriptInfo = New-PSScriptInfoObject -Path $scriptFilePath + + [System.Management.Automation.Language.Token[]]$tokens = $null; + [System.Management.Automation.Language.ParseError[]]$errors = $null; + $ast = [System.Management.Automation.Language.Parser]::ParseFile($scriptFilePath, ([ref]$tokens), ([ref]$errors)) + + + $notSupportedOnNanoErrorIds = @('WorkflowNotSupportedInPowerShellCore', + 'ConfigurationNotSupportedInPowerShellCore') + $errosAfterSkippingOneCoreErrors = $errors | Microsoft.PowerShell.Core\Where-Object { $notSupportedOnNanoErrorIds -notcontains $_.ErrorId} + + if($errosAfterSkippingOneCoreErrors) + { + $errorMessage = ($LocalizedData.ScriptParseError -f $scriptFilePath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "ScriptParseError" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $errosAfterSkippingOneCoreErrors ` + -ErrorCategory InvalidArgument + return + } + + if($ast) + { + # Get the block/group comment begining with <#PSScriptInfo + $CommentTokens = $tokens | Microsoft.PowerShell.Core\Where-Object {$_.Kind -eq 'Comment'} + + $psscriptInfoComments = $CommentTokens | + Microsoft.PowerShell.Core\Where-Object { $_.Extent.Text -match "<#PSScriptInfo" } | + Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $psscriptInfoComments) + { + $errorMessage = ($LocalizedData.MissingPSScriptInfo -f $scriptFilePath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "MissingPSScriptInfo" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $scriptFilePath ` + -ErrorCategory InvalidArgument + return + } + + # $psscriptInfoComments.Text will have the multiline PSScriptInfo comment, + # split them into multiple lines to parse for the PSScriptInfo metadata properties. + $commentLines = $psscriptInfoComments.Text -split "`r`n" + + $KeyName = $null + $Value = "" + + # PSScriptInfo comment will be in following format: + <#PSScriptInfo + + .VERSION 1.0 + + .GUID 544238e3-1751-4065-9227-be105ff11636 + + .AUTHOR manikb + + .COMPANYNAME Microsoft Corporation + + .COPYRIGHT (c) 2015 Microsoft Corporation. All rights reserved. + + .TAGS Tag1 Tag2 Tag3 + + .LICENSEURI https://contoso.com/License + + .PROJECTURI https://contoso.com/ + + .ICONURI https://contoso.com/Icon + + .EXTERNALMODULEDEPENDENCIES ExternalModule1 + + .REQUIREDSCRIPTS Start-WFContosoServer,Stop-ContosoServerScript + + .EXTERNALSCRIPTDEPENDENCIES Stop-ContosoServerScript + + .RELEASENOTES + contoso script now supports following features + Feature 1 + Feature 2 + Feature 3 + Feature 4 + Feature 5 + + #> + # If comment line count is not more than two, it doesn't have the any metadata property + # First line is <#PSScriptInfo + # Last line #> + # + if($commentLines.Count -gt 2) + { + for($i = 1; $i -lt ($commentLines.count - 1); $i++) + { + $line = $commentLines[$i] + + if(-not $line) + { + continue + } + + # A line is starting with . conveys a new metadata property + # __NEWLINE__ is used for replacing the value lines while adding the value to $PSScriptInfo object + # + if($line.trim().StartsWith('.')) + { + $parts = $line.trim() -split '[.\s+]',3 | Microsoft.PowerShell.Core\Where-Object {$_} + + if($KeyName -and $Value) + { + if($keyName -eq $script:ReleaseNotes) + { + $Value = $Value.Trim() -split '__NEWLINE__' + } + elseif($keyName -eq $script:DESCRIPTION) + { + $Value = $Value -split '__NEWLINE__' + $Value = ($Value -join "`r`n").Trim() + } + else + { + $Value = $Value -split '__NEWLINE__' | Microsoft.PowerShell.Core\Where-Object { $_ } + + if($Value -and $Value.GetType().ToString() -eq "System.String") + { + $Value = $Value.Trim() + } + } + + ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo ` + -PropertyName $KeyName ` + -PropertyValue $Value ` + -CallerPSCmdlet $PSCmdlet + } + + $KeyName = $null + $Value = "" + + if($parts.GetType().ToString() -eq "System.String") + { + $KeyName = $parts + } + else + { + $KeyName = $parts[0]; + $Value = $parts[1] + } + } + else + { + if($Value) + { + # __NEWLINE__ is used for replacing the value lines while adding the value to $PSScriptInfo object + $Value += '__NEWLINE__' + } + + $Value += $line + } + } + + if($KeyName -and $Value) + { + if($keyName -eq $script:ReleaseNotes) + { + $Value = $Value.Trim() -split '__NEWLINE__' + } + elseif($keyName -eq $script:DESCRIPTION) + { + $Value = $Value -split '__NEWLINE__' + $Value = ($Value -join "`r`n").Trim() + } + else + { + $Value = $Value -split '__NEWLINE__' | Microsoft.PowerShell.Core\Where-Object { $_ } + + if($Value -and $Value.GetType().ToString() -eq "System.String") + { + $Value = $Value.Trim() + } + } + + ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo ` + -PropertyName $KeyName ` + -PropertyValue $Value ` + -CallerPSCmdlet $PSCmdlet + + $KeyName = $null + $Value = "" + } + } + + $helpContent = $ast.GetHelpContent() + if($helpContent -and $helpContent.Description) + { + ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo ` + -PropertyName $script:DESCRIPTION ` + -PropertyValue $helpContent.Description.Trim() ` + -CallerPSCmdlet $PSCmdlet + + } + + # Handle RequiredModules + if((Microsoft.PowerShell.Utility\Get-Member -InputObject $ast -Name 'ScriptRequirements') -and + $ast.ScriptRequirements -and + (Microsoft.PowerShell.Utility\Get-Member -InputObject $ast.ScriptRequirements -Name 'RequiredModules') -and + $ast.ScriptRequirements.RequiredModules) + { + ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo ` + -PropertyName $script:RequiredModules ` + -PropertyValue $ast.ScriptRequirements.RequiredModules ` + -CallerPSCmdlet $PSCmdlet + } + + # Get all defined functions and populate DefinedCommands, DefinedFunctions and DefinedWorkflows + $allCommands = $ast.FindAll({param($i) return ($i.GetType().Name -eq 'FunctionDefinitionAst')}, $true) + + if($allCommands) + { + $allCommandNames = $allCommands | ForEach-Object {$_.Name} | Select-Object -Unique + ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo ` + -PropertyName $script:DefinedCommands ` + -PropertyValue $allCommandNames ` + -CallerPSCmdlet $PSCmdlet + + $allFunctionNames = $allCommands | Where-Object {-not $_.IsWorkflow} | ForEach-Object {$_.Name} | Select-Object -Unique + ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo ` + -PropertyName $script:DefinedFunctions ` + -PropertyValue $allFunctionNames ` + -CallerPSCmdlet $PSCmdlet + + + $allWorkflowNames = $allCommands | Where-Object {$_.IsWorkflow} | ForEach-Object {$_.Name} | Select-Object -Unique + ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo ` + -PropertyName $script:DefinedWorkflows ` + -PropertyValue $allWorkflowNames ` + -CallerPSCmdlet $PSCmdlet + } + } + + # Ensure that the script file has the required metadata properties. + if(-not $PSScriptInfo.Version -or -not $PSScriptInfo.Guid -or -not $PSScriptInfo.Author -or -not $PSScriptInfo.Description) + { + $errorMessage = ($LocalizedData.MissingRequiredPSScriptInfoProperties -f $scriptFilePath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "MissingRequiredPSScriptInfoProperties" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $Path ` + -ErrorCategory InvalidArgument + return + } + + $PSScriptInfo = Get-OrderedPSScriptInfoObject -PSScriptInfo $PSScriptInfo + + return $PSScriptInfo + } +} + +function New-ScriptFileInfo +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(PositionalBinding=$false, + SupportsShouldProcess=$true, + HelpUri='http://go.microsoft.com/fwlink/?LinkId=619792')] + Param + ( + [Parameter(Mandatory=$false, + Position=0, + ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Path, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Version] + $Version, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Author, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Description, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Guid] + $Guid, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String] + $CompanyName, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Copyright, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Object[]] + $RequiredModules, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String[]] + $ExternalModuleDependencies, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $RequiredScripts, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String[]] + $ExternalScriptDependencies, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Tags, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $ProjectUri, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $LicenseUri, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $IconUri, + + [Parameter()] + [string[]] + $ReleaseNotes, + + [Parameter()] + [switch] + $PassThru, + + [Parameter()] + [switch] + $Force + ) + + Process + { + if($Path) + { + if(-not $Path.EndsWith('.ps1', [System.StringComparison]::OrdinalIgnoreCase)) + { + $errorMessage = ($LocalizedData.InvalidScriptFilePath -f $Path) + ThrowError -ExceptionName 'System.ArgumentException' ` + -ExceptionMessage $errorMessage ` + -ErrorId 'InvalidScriptFilePath' ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $Path ` + -ErrorCategory InvalidArgument + return + } + + if(-not $Force -and (Microsoft.PowerShell.Management\Test-Path -Path $Path)) + { + $errorMessage = ($LocalizedData.ScriptFileExist -f $Path) + ThrowError -ExceptionName 'System.ArgumentException' ` + -ExceptionMessage $errorMessage ` + -ErrorId 'ScriptFileExist' ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $Path ` + -ErrorCategory InvalidArgument + return + } + } + elseif(-not $PassThru) + { + ThrowError -ExceptionName 'System.ArgumentException' ` + -ExceptionMessage $LocalizedData.MissingTheRequiredPathOrPassThruParameter ` + -ErrorId 'MissingTheRequiredPathOrPassThruParameter' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } + + if(-not $Version) + { + $Version = [Version]'1.0' + } + + if(-not $Author) + { + $Author = (Get-EnvironmentVariable -Name 'USERNAME' -Target $script:EnvironmentVariableTarget.Process -ErrorAction SilentlyContinue) + } + + if(-not $Guid) + { + $Guid = [System.Guid]::NewGuid() + } + + $params = @{ + Version = $Version + Author = $Author + Guid = $Guid + CompanyName = $CompanyName + Copyright = $Copyright + ExternalModuleDependencies = $ExternalModuleDependencies + RequiredScripts = $RequiredScripts + ExternalScriptDependencies = $ExternalScriptDependencies + Tags = $Tags + ProjectUri = $ProjectUri + LicenseUri = $LicenseUri + IconUri = $IconUri + ReleaseNotes = $ReleaseNotes + } + + if(-not (Validate-ScriptFileInfoParameters -parameters $params)) + { + return + } + + if("$Description" -match '<#' -or "$Description" -match '#>') + { + $message = $LocalizedData.InvalidParameterValue -f ($Description, 'Description') + Write-Error -Message $message -ErrorId 'InvalidParameterValue' -Category InvalidArgument + + return + } + + $PSScriptInfoString = Get-PSScriptInfoString @params + + $requiresStrings = Get-RequiresString -RequiredModules $RequiredModules + + $ScriptCommentHelpInfoString = Get-ScriptCommentHelpInfoString -Description $Description + + $ScriptMetadataString = $PSScriptInfoString + $ScriptMetadataString += "`r`n" + + if("$requiresStrings".Trim()) + { + $ScriptMetadataString += "`r`n" + $ScriptMetadataString += $requiresStrings -join "`r`n" + $ScriptMetadataString += "`r`n" + } + + $ScriptMetadataString += "`r`n" + $ScriptMetadataString += $ScriptCommentHelpInfoString + $ScriptMetadataString += "Param()`r`n`r`n" + + $tempScriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $env:TEMP -ChildPath "$(Get-Random).ps1" + + try + { + Microsoft.PowerShell.Management\Set-Content -Value $ScriptMetadataString -Path $tempScriptFilePath -Force -WhatIf:$false -Confirm:$false + + $scriptInfo = Test-ScriptFileInfo -Path $tempScriptFilePath + + if(-not $scriptInfo) + { + # Above Test-ScriptFileInfo cmdlet writes the errors + return + } + + if($Path -and ($Force -or $PSCmdlet.ShouldProcess($Path, ($LocalizedData.NewScriptFileInfowhatIfMessage -f $Path) ))) + { + Microsoft.PowerShell.Management\Copy-Item -Path $tempScriptFilePath -Destination $Path -Force -WhatIf:$false -Confirm:$false + } + + if($PassThru) + { + Write-Output -InputObject $ScriptMetadataString + } + } + finally + { + Microsoft.PowerShell.Management\Remove-Item -Path $tempScriptFilePath -Force -WhatIf:$false -Confirm:$false -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + } +} + +function Update-ScriptFileInfo +{ + <# + .ExternalHelp PSGet.psm1-help.xml + #> + [CmdletBinding(PositionalBinding=$false, + DefaultParameterSetName='PathParameterSet', + SupportsShouldProcess=$true, + HelpUri='http://go.microsoft.com/fwlink/?LinkId=619793')] + Param + ( + [Parameter(Mandatory=$true, + Position=0, + ParameterSetName='PathParameterSet', + ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Path, + + [Parameter(Mandatory=$true, + Position=0, + ParameterSetName='LiteralPathParameterSet', + ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string] + $LiteralPath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Version] + $Version, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Author, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Guid] + $Guid, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Description, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String] + $CompanyName, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Copyright, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Object[]] + $RequiredModules, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String[]] + $ExternalModuleDependencies, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $RequiredScripts, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String[]] + $ExternalScriptDependencies, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Tags, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $ProjectUri, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $LicenseUri, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $IconUri, + + [Parameter()] + [string[]] + $ReleaseNotes, + + [Parameter()] + [switch] + $PassThru, + + [Parameter()] + [switch] + $Force + ) + + Process + { + $scriptFilePath = $null + if($Path) + { + $scriptFilePath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | + Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $scriptFilePath -or + -not (Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $Path) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $Path ` + -ErrorCategory InvalidArgument + } + } + else + { + $scriptFilePath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet | + Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $scriptFilePath -or + -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $scriptFilePath -PathType Leaf)) + { + $errorMessage = ($LocalizedData.PathNotFound -f $LiteralPath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $LiteralPath ` + -ErrorCategory InvalidArgument + } + } + + if(-not $scriptFilePath.EndsWith('.ps1', [System.StringComparison]::OrdinalIgnoreCase)) + { + $errorMessage = ($LocalizedData.InvalidScriptFilePath -f $scriptFilePath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "InvalidScriptFilePath" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $scriptFilePath ` + -ErrorCategory InvalidArgument + return + } + + $psscriptInfo = $null + try + { + $psscriptInfo = Test-ScriptFileInfo -LiteralPath $scriptFilePath + } + catch + { + if(-not $Force) + { + throw $_ + return + } + } + + if(-not $psscriptInfo) + { + if(-not $Description) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.DescriptionParameterIsMissingForAddingTheScriptFileInfo ` + -ErrorId 'DescriptionParameterIsMissingForAddingTheScriptFileInfo' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } + + if(-not $Version) + { + $Version = [Version]'1.0' + } + + if(-not $Author) + { + $Author = (Get-EnvironmentVariable -Name 'USERNAME' -Target $script:EnvironmentVariableTarget.Process -ErrorAction SilentlyContinue) + } + + if(-not $Guid) + { + $Guid = [System.Guid]::NewGuid() + } + } + else + { + # Use existing values if any of the parameters are not specified during Update-ScriptFileInfo + if(-not $Version -and $psscriptInfo.Version) + { + $Version = $psscriptInfo.Version + } + + if(-not $Guid -and $psscriptInfo.Guid) + { + $Guid = $psscriptInfo.Guid + } + + if(-not $Author -and $psscriptInfo.Author) + { + $Author = $psscriptInfo.Author + } + + if(-not $CompanyName -and $psscriptInfo.CompanyName) + { + $CompanyName = $psscriptInfo.CompanyName + } + + if(-not $Copyright -and $psscriptInfo.Copyright) + { + $Copyright = $psscriptInfo.Copyright + } + + if(-not $RequiredModules -and $psscriptInfo.RequiredModules) + { + $RequiredModules = $psscriptInfo.RequiredModules + } + + if(-not $ExternalModuleDependencies -and $psscriptInfo.ExternalModuleDependencies) + { + $ExternalModuleDependencies = $psscriptInfo.ExternalModuleDependencies + } + + if(-not $RequiredScripts -and $psscriptInfo.RequiredScripts) + { + $RequiredScripts = $psscriptInfo.RequiredScripts + } + + if(-not $ExternalScriptDependencies -and $psscriptInfo.ExternalScriptDependencies) + { + $ExternalScriptDependencies = $psscriptInfo.ExternalScriptDependencies + } + + if(-not $Tags -and $psscriptInfo.Tags) + { + $Tags = $psscriptInfo.Tags + } + + if(-not $ProjectUri -and $psscriptInfo.ProjectUri) + { + $ProjectUri = $psscriptInfo.ProjectUri + } + + if(-not $LicenseUri -and $psscriptInfo.LicenseUri) + { + $LicenseUri = $psscriptInfo.LicenseUri + } + + if(-not $IconUri -and $psscriptInfo.IconUri) + { + $IconUri = $psscriptInfo.IconUri + } + + if(-not $ReleaseNotes -and $psscriptInfo.ReleaseNotes) + { + $ReleaseNotes = $psscriptInfo.ReleaseNotes + } + } + + $params = @{ + Version = $Version + Author = $Author + Guid = $Guid + CompanyName = $CompanyName + Copyright = $Copyright + ExternalModuleDependencies = $ExternalModuleDependencies + RequiredScripts = $RequiredScripts + ExternalScriptDependencies = $ExternalScriptDependencies + Tags = $Tags + ProjectUri = $ProjectUri + LicenseUri = $LicenseUri + IconUri = $IconUri + ReleaseNotes = $ReleaseNotes + } + + if(-not (Validate-ScriptFileInfoParameters -parameters $params)) + { + return + } + + if("$Description" -match '<#' -or "$Description" -match '#>') + { + $message = $LocalizedData.InvalidParameterValue -f ($Description, 'Description') + Write-Error -Message $message -ErrorId 'InvalidParameterValue' -Category InvalidArgument + + return + } + + $PSScriptInfoString = Get-PSScriptInfoString @params + + $requiresStrings = "" + $requiresStrings = Get-RequiresString -RequiredModules $RequiredModules + + $DescriptionValue = if($Description) {$Description} else {$psscriptInfo.Description} + $ScriptCommentHelpInfoString = Get-ScriptCommentHelpInfoString -Description $DescriptionValue + + $ScriptMetadataString = $PSScriptInfoString + $ScriptMetadataString += "`r`n" + + if("$requiresStrings".Trim()) + { + $ScriptMetadataString += "`r`n" + $ScriptMetadataString += $requiresStrings -join "`r`n" + $ScriptMetadataString += "`r`n" + } + + $ScriptMetadataString += "`r`n" + $ScriptMetadataString += $ScriptCommentHelpInfoString + $ScriptMetadataString += "`r`nParam()`r`n`r`n" + if(-not $ScriptMetadataString) + { + return + } + + $tempScriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $env:TEMP -ChildPath "$(Get-Random).ps1" + + try + { + # First create a new script file with new script metadata to ensure that updated values are valid. + Microsoft.PowerShell.Management\Set-Content -Value $ScriptMetadataString -Path $tempScriptFilePath -Force -WhatIf:$false -Confirm:$false + + $scriptInfo = Test-ScriptFileInfo -Path $tempScriptFilePath + + if(-not $scriptInfo) + { + # Above Test-ScriptFileInfo cmdlet writes the error + return + } + + $scriptFileContents = Microsoft.PowerShell.Management\Get-Content -LiteralPath $scriptFilePath + + # If -Force is specified and script file doesnt have a valid PSScriptInfo + # Prepend the PSScriptInfo and Check if the Test-ScriptFileInfo returns a valid script info without any errors + if($Force -and -not $psscriptInfo) + { + # Add the script file contents to the temp file with script metadata + Microsoft.PowerShell.Management\Set-Content -LiteralPath $tempScriptFilePath ` + -Value $ScriptMetadataString,$scriptFileContents ` + -Force ` + -WhatIf:$false ` + -Confirm:$false + + $tempScriptInfo = $null + try + { + $tempScriptInfo = Test-ScriptFileInfo -LiteralPath $tempScriptFilePath + } + catch + { + $errorMessage = ($LocalizedData.UnableToAddPSScriptInfo -f $scriptFilePath) + ThrowError -ExceptionName 'System.InvalidOperationException' ` + -ExceptionMessage $errorMessage ` + -ErrorId 'UnableToAddPSScriptInfo' ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $scriptFilePath ` + -ErrorCategory InvalidOperation + return + } + } + else + { + [System.Management.Automation.Language.Token[]]$tokens = $null; + [System.Management.Automation.Language.ParseError[]]$errors = $null; + $ast = [System.Management.Automation.Language.Parser]::ParseFile($scriptFilePath, ([ref]$tokens), ([ref]$errors)) + + # Update PSScriptInfo and #Requires + $CommentTokens = $tokens | Microsoft.PowerShell.Core\Where-Object {$_.Kind -eq 'Comment'} + + $psscriptInfoComments = $CommentTokens | + Microsoft.PowerShell.Core\Where-Object { $_.Extent.Text -match "<#PSScriptInfo" } | + Microsoft.PowerShell.Utility\Select-Object -First 1 + + if(-not $psscriptInfoComments) + { + $errorMessage = ($LocalizedData.MissingPSScriptInfo -f $scriptFilePath) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "MissingPSScriptInfo" ` + -CallerPSCmdlet $PSCmdlet ` + -ExceptionObject $scriptFilePath ` + -ErrorCategory InvalidArgument + return + } + + # Ensure that metadata is replaced at the correct location and should not corrupt the existing script file. + + # Remove the lines between below lines and add the new PSScriptInfo and new #Requires statements + # ($psscriptInfoComments.Extent.StartLineNumber - 1) + # ($psscriptInfoComments.Extent.EndLineNumber - 1) + $tempContents = @() + $IsNewPScriptInfoAdded = $false + + for($i = 0; $i -lt $scriptFileContents.Count; $i++) + { + $line = $scriptFileContents[$i] + if(($i -ge ($psscriptInfoComments.Extent.StartLineNumber - 1)) -and + ($i -le ($psscriptInfoComments.Extent.EndLineNumber - 1))) + { + if(-not $IsNewPScriptInfoAdded) + { + $PSScriptInfoString = $PSScriptInfoString.TrimStart() + $requiresStrings = $requiresStrings.TrimEnd() + + $tempContents += "$PSScriptInfoString `r`n`r`n$($requiresStrings -join "`r`n")" + $IsNewPScriptInfoAdded = $true + } + } + elseif($line -notmatch "\s*#Requires\s+-Module") + { + # Add the existing lines if they are not part of PSScriptInfo comment or not containing #Requires -Module statements. + $tempContents += $line + } + } + + Microsoft.PowerShell.Management\Set-Content -Value $tempContents -Path $tempScriptFilePath -Force -WhatIf:$false -Confirm:$false + + $scriptInfo = Test-ScriptFileInfo -Path $tempScriptFilePath + + if(-not $scriptInfo) + { + # Above Test-ScriptFileInfo cmdlet writes the error + return + } + + # Now update the Description value if a new is specified. + if($Description) + { + $tempContents = @() + $IsDescriptionAdded = $false + + $IsDescriptionBeginFound = $false + $scriptFileContents = Microsoft.PowerShell.Management\Get-Content -Path $tempScriptFilePath + + for($i = 0; $i -lt $scriptFileContents.Count; $i++) + { + $line = $scriptFileContents[$i] + + if(-not $IsDescriptionAdded) + { + if(-not $IsDescriptionBeginFound) + { + if($line.Trim().StartsWith(".DESCRIPTION", [System.StringComparison]::OrdinalIgnoreCase)) + { + $IsDescriptionBeginFound = $true + } + else + { + $tempContents += $line + } + } + else + { + # Description begin has found + # Skip the old description lines until description end is found + + if($line.Trim().StartsWith("#>", [System.StringComparison]::OrdinalIgnoreCase) -or + $line.Trim().StartsWith(".", [System.StringComparison]::OrdinalIgnoreCase)) + { + $tempContents += ".DESCRIPTION `r`n$($Description -join "`r`n")`r`n" + $IsDescriptionAdded = $true + $tempContents += $line + } + } + } + else + { + $tempContents += $line + } + } + + Microsoft.PowerShell.Management\Set-Content -Value $tempContents -Path $tempScriptFilePath -Force -WhatIf:$false -Confirm:$false + + $scriptInfo = Test-ScriptFileInfo -Path $tempScriptFilePath + + if(-not $scriptInfo) + { + # Above Test-ScriptFileInfo cmdlet writes the error + return + } + } + } + + if($Force -or $PSCmdlet.ShouldProcess($scriptFilePath, ($LocalizedData.UpdateScriptFileInfowhatIfMessage -f $Path) )) + { + Microsoft.PowerShell.Management\Copy-Item -Path $tempScriptFilePath -Destination $scriptFilePath -Force -WhatIf:$false -Confirm:$false + + if($PassThru) + { + $ScriptMetadataString + } + } + } + finally + { + Microsoft.PowerShell.Management\Remove-Item -Path $tempScriptFilePath -Force -WhatIf:$false -Confirm:$false -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + } +} + +function Get-RequiresString +{ + [CmdletBinding()] + Param + ( + [Parameter()] + [Object[]] + $RequiredModules + ) + + Process + { + if($RequiredModules) + { + $RequiredModuleStrings = @() + + foreach($requiredModuleObject in $RequiredModules) + { + if($requiredModuleObject.GetType().ToString() -eq 'System.Collections.Hashtable') + { + if(($requiredModuleObject.Keys.Count -eq 1) -and + (Microsoft.PowerShell.Utility\Get-Member -InputObject $requiredModuleObject -Name 'ModuleName')) + { + $RequiredModuleStrings += $requiredModuleObject['ModuleName'].ToString() + } + else + { + $moduleSpec = New-Object Microsoft.PowerShell.Commands.ModuleSpecification -ArgumentList $requiredModuleObject + if (-not (Microsoft.PowerShell.Utility\Get-Variable -Name moduleSpec -ErrorAction SilentlyContinue)) + { + return + } + + $keyvalueStrings = $requiredModuleObject.Keys | Microsoft.PowerShell.Core\ForEach-Object {"$_ = '$( $requiredModuleObject[$_])'"} + $RequiredModuleStrings += "@{$($keyvalueStrings -join '; ')}" + } + } + elseif(($PSVersionTable.PSVersion -eq [Version]'3.0') -and + ($requiredModuleObject.GetType().ToString() -eq 'Microsoft.PowerShell.Commands.ModuleSpecification')) + { + # ModuleSpecification.ToString() is not implemented on PowerShell 3.0. + + $optionalString = " " + + if($requiredModuleObject.Version) + { + $optionalString += "ModuleVersion = '$($requiredModuleObject.Version.ToString())'; " + } + + if($requiredModuleObject.Guid) + { + $optionalString += "Guid = '$($requiredModuleObject.Guid.ToString())'; " + } + + if($optionalString.Trim()) + { + $moduleSpecString = "@{ ModuleName = '$($requiredModuleObject.Name.ToString())';$optionalString}" + } + else + { + $moduleSpecString = $requiredModuleObject.Name.ToString() + } + + $RequiredModuleStrings += $moduleSpecString + } + else + { + $RequiredModuleStrings += $requiredModuleObject.ToString() + } + } + + $hashRequiresStrings = $RequiredModuleStrings | + Microsoft.PowerShell.Core\ForEach-Object { "#Requires -Module $_" } + + return $hashRequiresStrings + } + else + { + return "" + } + } +} + +function Get-PSScriptInfoString +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [Version] + $Version, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [Guid] + $Guid, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Author, + + [Parameter()] + [String] + $CompanyName, + + [Parameter()] + [string] + $Copyright, + + [Parameter()] + [String[]] + $ExternalModuleDependencies, + + [Parameter()] + [string[]] + $RequiredScripts, + + [Parameter()] + [String[]] + $ExternalScriptDependencies, + + [Parameter()] + [string[]] + $Tags, + + [Parameter()] + [Uri] + $ProjectUri, + + [Parameter()] + [Uri] + $LicenseUri, + + [Parameter()] + [Uri] + $IconUri, + + [Parameter()] + [string[]] + $ReleaseNotes + ) + + Process + { + $PSScriptInfoString = @" + +<#PSScriptInfo + +.VERSION $Version + +.GUID $Guid + +.AUTHOR $Author + +.COMPANYNAME $CompanyName + +.COPYRIGHT $Copyright + +.TAGS $Tags + +.LICENSEURI $LicenseUri + +.PROJECTURI $ProjectUri + +.ICONURI $IconUri + +.EXTERNALMODULEDEPENDENCIES $($ExternalModuleDependencies -join ',') + +.REQUIREDSCRIPTS $($RequiredScripts -join ',') + +.EXTERNALSCRIPTDEPENDENCIES $($ExternalScriptDependencies -join ',') + +.RELEASENOTES +$($ReleaseNotes -join "`r`n") + +#> +"@ + return $PSScriptInfoString + } +} + +function Validate-ScriptFileInfoParameters +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [PSCustomObject] + $Parameters + ) + + $hasErrors = $false + + $Parameters.Keys | ForEach-Object { + + $parameterName = $_ + + $parameterValue = $($Parameters[$parameterName]) + + if("$parameterValue" -match '<#' -or "$parameterValue" -match '#>') + { + $message = $LocalizedData.InvalidParameterValue -f ($parameterValue, $parameterName) + Write-Error -Message $message -ErrorId 'InvalidParameterValue' -Category InvalidArgument + + $hasErrors = $true + } + } + + return (-not $hasErrors) +} + +function Get-ScriptCommentHelpInfoString +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Description, + + [Parameter()] + [string] + $Synopsis, + + [Parameter()] + [string[]] + $Example, + + [Parameter()] + [string[]] + $Inputs, + + [Parameter()] + [string[]] + $Outputs, + + [Parameter()] + [string[]] + $Notes, + + [Parameter()] + [string[]] + $Link, + + [Parameter()] + [string] + $Component, + + [Parameter()] + [string] + $Role, + + [Parameter()] + [string] + $Functionality + ) + + Process + { + $ScriptCommentHelpInfoString = "<# `r`n`r`n.DESCRIPTION `r`n $Description `r`n`r`n" + + if("$Synopsis".Trim()) + { + $ScriptCommentHelpInfoString += ".SYNOPSIS `r`n$Synopsis `r`n`r`n" + } + + if("$Example".Trim()) + { + $Example | ForEach-Object { + if($_) + { + $ScriptCommentHelpInfoString += ".EXAMPLE `r`n$_ `r`n`r`n" + } + } + } + + if("$Inputs".Trim()) + { + $Inputs | ForEach-Object { + if($_) + { + $ScriptCommentHelpInfoString += ".INPUTS `r`n$_ `r`n`r`n" + } + } + } + + if("$Outputs".Trim()) + { + $Outputs | ForEach-Object { + if($_) + { + $ScriptCommentHelpInfoString += ".OUTPUTS `r`n$_ `r`n`r`n" + } + } + } + + if("$Notes".Trim()) + { + $ScriptCommentHelpInfoString += ".NOTES `r`n$($Notes -join "`r`n") `r`n`r`n" + } + + if("$Link".Trim()) + { + $Link | ForEach-Object { + if($_) + { + $ScriptCommentHelpInfoString += ".LINK `r`n$_ `r`n`r`n" + } + } + } + + if("$Component".Trim()) + { + $ScriptCommentHelpInfoString += ".COMPONENT `r`n$($Component -join "`r`n") `r`n`r`n" + } + + if("$Role".Trim()) + { + $ScriptCommentHelpInfoString += ".ROLE `r`n$($Role -join "`r`n") `r`n`r`n" + } + + if("$Functionality".Trim()) + { + $ScriptCommentHelpInfoString += ".FUNCTIONALITY `r`n$($Functionality -join "`r`n") `r`n`r`n" + } + + $ScriptCommentHelpInfoString += "#> `r`n" + + return $ScriptCommentHelpInfoString + } +} + +#endregion *-ScriptFileInfo cmdlets + +#region Utility functions +function ToUpper +{ + param([string]$str) + return $script:TextInfo.ToUpper($str) +} + +function Resolve-PathHelper +{ + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $path, + + [Parameter()] + [switch] + $isLiteralPath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCmdlet] + $callerPSCmdlet + ) + + $resolvedPaths =@() + + foreach($currentPath in $path) + { + try + { + if($isLiteralPath) + { + $currentResolvedPaths = Microsoft.PowerShell.Management\Resolve-Path -LiteralPath $currentPath -ErrorAction Stop + } + else + { + $currentResolvedPaths = Microsoft.PowerShell.Management\Resolve-Path -Path $currentPath -ErrorAction Stop + } + } + catch + { + $errorMessage = ($LocalizedData.PathNotFound -f $currentPath) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $errorMessage ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $callerPSCmdlet ` + -ErrorCategory InvalidOperation + } + + foreach($currentResolvedPath in $currentResolvedPaths) + { + $resolvedPaths += $currentResolvedPath.ProviderPath + } + } + + $resolvedPaths +} + +function Check-PSGalleryApiAvailability +{ + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $PSGalleryV2ApiUri, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $PSGalleryV3ApiUri + ) + + # check internet availability first + $connected = $false + if(Get-Command Microsoft.PowerShell.Management\Test-Connection -ErrorAction SilentlyContinue) + { + $connected = Microsoft.PowerShell.Management\Test-Connection -ComputerName "www.microsoft.com" -Count 1 -Quiet + } + else + { + $connected = NetTCPIP\Test-NetConnection -ComputerName "www.microsoft.com" -InformationLevel Quiet + } + if ( -not $connected) + { + return + } + + $statusCode_v2 = $null + $resolvedUri_v2 = $null + $statusCode_v3 = $null + $resolvedUri_v3 = $null + + # ping V2 + $res_v2 = Ping-Endpoint -Endpoint $PSGalleryV2ApiUri + if ($res_v2.ContainsKey($Script:ResponseUri)) + { + $resolvedUri_v2 = $res_v2[$Script:ResponseUri] + } + if ($res_v2.ContainsKey($Script:StatusCode)) + { + $statusCode_v2 = $res_v2[$Script:StatusCode] + } + + + # ping V3 + $res_v3 = Ping-Endpoint -Endpoint $PSGalleryV3ApiUri + if ($res_v3.ContainsKey($Script:ResponseUri)) + { + $resolvedUri_v3 = $res_v3[$Script:ResponseUri] + } + if ($res_v3.ContainsKey($Script:StatusCode)) + { + $statusCode_v3 = $res_v3[$Script:StatusCode] + } + + + $Script:PSGalleryV2ApiAvailable = (($statusCode_v2 -eq 200) -and ($resolvedUri_v2)) + $Script:PSGalleryV3ApiAvailable = (($statusCode_v3 -eq 200) -and ($resolvedUri_v3)) + $Script:PSGalleryApiChecked = $true +} + +function Get-PSGalleryApiAvailability +{ + param + ( + [Parameter()] + [string[]] + $Repository + ) + + # skip if repository is null or not PSGallery + if ( -not $Repository) + { + return + } + + if ($Repository -notcontains $Script:PSGalleryModuleSource ) + { + return + } + + # run check only once + if( -not $Script:PSGalleryApiChecked) + { + $null = Check-PSGalleryApiAvailability -PSGalleryV2ApiUri $Script:PSGallerySourceUri -PSGalleryV3ApiUri $Script:PSGalleryV3SourceUri + } + + if ( -not $Script:PSGalleryV2ApiAvailable ) + { + if ($Script:PSGalleryV3ApiAvailable) + { + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $LocalizedData.PSGalleryApiV2Discontinued ` + -ErrorId "PSGalleryApiV2Discontinued" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + } + else + { + # both APIs are down, throw error + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $LocalizedData.PowerShellGalleryUnavailable ` + -ErrorId "PowerShellGalleryUnavailable" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + } + + } + else + { + if ($Script:PSGalleryV3ApiAvailable) + { + Write-Warning -Message $LocalizedData.PSGalleryApiV2Deprecated + return + } + } + + # if V2 is available and V3 is not available, do nothing +} + +function HttpClientApisAvailable +{ + $HttpClientApisAvailable = $false + try + { + [System.Net.Http.HttpClient] + $HttpClientApisAvailable = $true + } + catch + { + } + return $HttpClientApisAvailable +} + +function Ping-Endpoint +{ + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Endpoint, + + [Parameter()] + [switch] + $AllowAutoRedirect = $true + ) + + $results = @{} + + if(HttpClientApisAvailable) + { + $response = $null + try + { + $handler = New-Object System.Net.Http.HttpClientHandler + $handler.UseDefaultCredentials = $true + $httpClient = New-Object System.Net.Http.HttpClient -ArgumentList $handler + $response = $httpclient.GetAsync($endpoint) + } + catch + { + } + + if ($response -ne $null -and $response.result -ne $null) + { + $results.Add($Script:ResponseUri,$response.Result.RequestMessage.RequestUri.AbsoluteUri.ToString()) + $results.Add($Script:StatusCode,$response.result.StatusCode.value__) + } + } + else + { + $iss = [System.Management.Automation.Runspaces.InitialSessionState]::Create() + $iss.types.clear() + $iss.formats.clear() + $iss.LanguageMode = "FullLanguage" + + $WebRequestcmd = @' + try + {{ + $request = [System.Net.WebRequest]::Create("{0}") + $request.Method = 'GET' + $request.Timeout = 30000 + $request.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials + $request.AllowAutoRedirect = ${1} + $response = [System.Net.HttpWebResponse]$request.GetResponse() + if($response.StatusCode.value__ -eq 302) + {{ + $response.Headers["Location"].ToString() + }} + else + {{ + $response + }} + $response.Close() + }} + catch [System.Net.WebException] + {{ + "Error:System.Net.WebException" + }} +'@ -f $EndPoint, $AllowAutoRedirect + + $ps = [powershell]::Create($iss).AddScript($WebRequestcmd) + $response = $ps.Invoke() + $ps.dispose() + if ($response -ne "Error:System.Net.WebException") + { + if($AllowAutoRedirect) + { + $results.Add($Script:ResponseUri,$response.ResponseUri.ToString()) + $results.Add($Script:StatusCode,$response.StatusCode.value__) + } + else + { + $results.Add($Script:ResponseUri,[String]$response) + } + } + } + return $results +} + +function Validate-VersionParameters +{ + Param( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCmdlet] + $CallerPSCmdlet, + + [Parameter()] + [String[]] + $Name, + + [Parameter()] + [Version] + $MinimumVersion, + + [Parameter()] + [Version] + $RequiredVersion, + + [Parameter()] + [Version] + $MaximumVersion, + + [Parameter()] + [Switch] + $AllVersions, + + [Parameter()] + [Switch] + $TestWildcardsInName + ) + + if($TestWildcardsInName -and $Name -and (Test-WildcardPattern -Name "$Name")) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage ($LocalizedData.NameShouldNotContainWildcardCharacters -f "$($Name -join ',')") ` + -ErrorId 'NameShouldNotContainWildcardCharacters' ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Name + } + elseif($AllVersions -and ($RequiredVersion -or $MinimumVersion -or $MaximumVersion)) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.AllVersionsCannotBeUsedWithOtherVersionParameters ` + -ErrorId 'AllVersionsCannotBeUsedWithOtherVersionParameters' ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument + } + elseif($RequiredVersion -and ($MinimumVersion -or $MaximumVersion)) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.VersionRangeAndRequiredVersionCannotBeSpecifiedTogether ` + -ErrorId "VersionRangeAndRequiredVersionCannotBeSpecifiedTogether" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument + } + elseif($MinimumVersion -and $MaximumVersion -and ($MinimumVersion -gt $MaximumVersion)) + { + $Message = $LocalizedData.MinimumVersionIsGreaterThanMaximumVersion -f ($MinimumVersion, $MaximumVersion) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $Message ` + -ErrorId "MinimumVersionIsGreaterThanMaximumVersion" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument + } + elseif($AllVersions -or $RequiredVersion -or $MinimumVersion -or $MaximumVersion) + { + if(-not $Name -or $Name.Count -ne 1 -or (Test-WildcardPattern -Name $Name[0])) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.VersionParametersAreAllowedOnlyWithSingleName ` + -ErrorId "VersionParametersAreAllowedOnlyWithSingleName" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument + } + } + + return $true +} + +function ValidateAndSet-PATHVariableIfUserAccepts +{ + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$true)] + [string] + $Scope, + + [Parameter(Mandatory=$true)] + [string] + $ScopePath, + + [Parameter()] + [Switch] + $NoPathUpdate, + + [Parameter()] + [Switch] + $Force, + + [Parameter()] + $Request + ) + + Set-PSGetSettingsVariable + + # Check and add the scope path to PATH environment variable if USER accepts the prompt. + if($Scope -eq 'AllUsers') + { + $envVariableTarget = $script:EnvironmentVariableTarget.Machine + $scriptPATHPromptQuery=$LocalizedData.ScriptPATHPromptQuery -f $ScopePath + $scopeSpecificKey = 'AllUsersScope_AllowPATHChangeForScripts' + } + else + { + $envVariableTarget = $script:EnvironmentVariableTarget.User + $scriptPATHPromptQuery=$LocalizedData.ScriptPATHPromptQuery -f $ScopePath + $scopeSpecificKey = 'CurrentUserScope_AllowPATHChangeForScripts' + } + + $AlreadyPromptedForScope = $script:PSGetSettings.Contains($scopeSpecificKey) + Write-Debug "Already prompted for the current scope:$AlreadyPromptedForScope" + + if(-not $AlreadyPromptedForScope) + { + # Read the file contents once again to ensure that it was not set in another PowerShell Session + Set-PSGetSettingsVariable -Force + + $AlreadyPromptedForScope = $script:PSGetSettings.Contains($scopeSpecificKey) + Write-Debug "After reading contents of PowerShellGetSettings.xml file, the Already prompted for the current scope:$AlreadyPromptedForScope" + + if($AlreadyPromptedForScope) + { + return + } + + $userResponse = $false + + if(-not $NoPathUpdate) + { + $scopePathEndingWithBackSlash = "$scopePath\" + + # Check and add the $scopePath to $env:Path value + if( (($env:PATH -split ';') -notcontains $scopePath) -and + (($env:PATH -split ';') -notcontains $scopePathEndingWithBackSlash)) + { + if($Force) + { + $userResponse = $true + } + else + { + $scriptPATHPromptCaption = $LocalizedData.ScriptPATHPromptCaption + + if($Request) + { + $userResponse = $Request.ShouldContinue($scriptPATHPromptQuery, $scriptPATHPromptCaption) + } + else + { + $userResponse = $PSCmdlet.ShouldContinue($scriptPATHPromptQuery, $scriptPATHPromptCaption) + } + } + + if($userResponse) + { + $currentPATHValue = Get-EnvironmentVariable -Name 'PATH' -Target $envVariableTarget + + if((($currentPATHValue -split ';') -notcontains $scopePath) -and + (($currentPATHValue -split ';') -notcontains $scopePathEndingWithBackSlash)) + { + # To ensure that the installed script is immediately usable, + # we need to add the scope path to the PATH enviroment variable. + Set-EnvironmentVariable -Name 'PATH' ` + -Value "$currentPATHValue;$scopePath" ` + -Target $envVariableTarget + + Write-Verbose ($LocalizedData.AddedScopePathToPATHVariable -f ($scopePath,$Scope)) + } + + # Process specific PATH + # Check and add the $scopePath to $env:Path value of current process + # so that installed scripts can be used in the current process. + $target = $script:EnvironmentVariableTarget.Process + $currentPATHValue = Get-EnvironmentVariable -Name 'PATH' -Target $target + + if((($currentPATHValue -split ';') -notcontains $scopePath) -and + (($currentPATHValue -split ';') -notcontains $scopePathEndingWithBackSlash)) + { + # To ensure that the installed script is immediately usable, + # we need to add the scope path to the PATH enviroment variable. + Set-EnvironmentVariable -Name 'PATH' ` + -Value "$currentPATHValue;$scopePath" ` + -Target $target + + Write-Verbose ($LocalizedData.AddedScopePathToProcessSpecificPATHVariable -f ($scopePath,$Scope)) + } + } + } + } + + # Add user's response to the PowerShellGet.settings file + $script:PSGetSettings[$scopeSpecificKey] = $userResponse + + Save-PSGetSettings + } +} + +function Save-PSGetSettings +{ + if($script:PSGetSettings) + { + if(-not (Microsoft.PowerShell.Management\Test-Path -Path $script:PSGetAppLocalPath)) + { + $null = Microsoft.PowerShell.Management\New-Item -Path $script:PSGetAppLocalPath ` + -ItemType Directory ` + -Force ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false ` + -WhatIf:$false + } + + Microsoft.PowerShell.Utility\Out-File -FilePath $script:PSGetSettingsFilePath -Force ` + -InputObject ([System.Management.Automation.PSSerializer]::Serialize($script:PSGetSettings)) + + Write-Debug "In Save-PSGetSettings, persisted the $script:PSGetSettingsFilePath file" + } +} + +function Set-PSGetSettingsVariable +{ + [CmdletBinding()] + param([switch]$Force) + + if(-not $script:PSGetSettings -or $Force) + { + if(Microsoft.PowerShell.Management\Test-Path -Path $script:PSGetSettingsFilePath) + { + $script:PSGetSettings = DeSerialize-PSObject -Path $script:PSGetSettingsFilePath + } + else + { + $script:PSGetSettings = [ordered]@{} + } + } +} + +function Set-ModuleSourcesVariable +{ + [CmdletBinding()] + param([switch]$Force) + + if(-not $script:PSGetModuleSources -or $Force) + { + $isPersistRequired = $false + if(Microsoft.PowerShell.Management\Test-Path $script:PSGetModuleSourcesFilePath) + { + $script:PSGetModuleSources = DeSerialize-PSObject -Path $script:PSGetModuleSourcesFilePath + } + else + { + $script:PSGetModuleSources = [ordered]@{} + + if(-not $script:PSGetModuleSources.Contains($Script:PSGalleryModuleSource)) + { + $isPersistRequired = $true + $psgalleryLocation = Resolve-Location -Location $Script:PSGallerySourceUri ` + -LocationParameterName 'SourceLocation' ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + + $scriptSourceLocation = Resolve-Location -Location $Script:PSGalleryScriptSourceUri ` + -LocationParameterName 'ScriptSourceLocation' ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + if($psgalleryLocation) + { + $result = Ping-Endpoint -Endpoint $Script:PSGalleryPublishUri -AllowAutoRedirect:$false + if ($result.ContainsKey($Script:ResponseUri) -and $result[$Script:ResponseUri]) + { + $script:PSGalleryPublishUri = $result[$Script:ResponseUri] + } + $moduleSource = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + Name = $Script:PSGalleryModuleSource + SourceLocation = $psgalleryLocation + PublishLocation = $Script:PSGalleryPublishUri + ScriptSourceLocation = $scriptSourceLocation + ScriptPublishLocation = $Script:PSGalleryPublishUri + Trusted=$false + Registered=$true + InstallationPolicy = 'Untrusted' + PackageManagementProvider=$script:NuGetProviderName + ProviderOptions = @{} + }) + + $moduleSource.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.PSRepository") + $script:PSGetModuleSources.Add($Script:PSGalleryModuleSource, $moduleSource) + } + } + } + + # Already registed repositories may not have the ScriptSourceLocation property, try to populate it from the existing SourceLocation + # Also populate the PublishLocation and ScriptPublishLocation from the SourceLocation if PublishLocation is empty/null. + # + $script:PSGetModuleSources.Keys | Microsoft.PowerShell.Core\ForEach-Object { + $moduleSource = $script:PSGetModuleSources[$_] + + if(-not (Get-Member -InputObject $moduleSource -Name $script:ScriptSourceLocation)) + { + $scriptSourceLocation = Get-ScriptSourceLocation -Location $moduleSource.SourceLocation + + Microsoft.PowerShell.Utility\Add-Member -InputObject $script:PSGetModuleSources[$_] ` + -MemberType NoteProperty ` + -Name $script:ScriptSourceLocation ` + -Value $scriptSourceLocation + + if(Get-Member -InputObject $moduleSource -Name $script:PublishLocation) + { + if(-not $moduleSource.PublishLocation) + { + $script:PSGetModuleSources[$_].PublishLocation = Get-PublishLocation -Location $moduleSource.SourceLocation + } + + Microsoft.PowerShell.Utility\Add-Member -InputObject $script:PSGetModuleSources[$_] ` + -MemberType NoteProperty ` + -Name $script:ScriptPublishLocation ` + -Value $moduleSource.PublishLocation + } + + $isPersistRequired = $true + } + } + + if($isPersistRequired) + { + Save-ModuleSources + } + } +} + +function Get-PackageManagementProviderName +{ + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Location + ) + + $PackageManagementProviderName = $null + $loc = Get-LocationString -LocationUri $Location + + $providers = PackageManagement\Get-PackageProvider | Where-Object { $_.Features.ContainsKey($script:SupportsPSModulesFeatureName) } + + foreach($provider in $providers) + { + # Skip the PowerShellGet provider + if($provider.ProviderName -eq $script:PSModuleProviderName) + { + continue + } + + $packageSource = Get-PackageSource -Location $loc -Provider $provider.ProviderName -ErrorAction SilentlyContinue + + if($packageSource) + { + $PackageManagementProviderName = $provider.ProviderName + break + } + } + + return $PackageManagementProviderName +} + +function Get-ProviderName +{ + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$true)] + [PSCustomObject] + $PSCustomObject + ) + + $providerName = $script:NuGetProviderName + + if((Get-Member -InputObject $PSCustomObject -Name PackageManagementProvider)) + { + $providerName = $PSCustomObject.PackageManagementProvider + } + + return $providerName +} + +function Get-DynamicParameters +{ + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Location, + + [Parameter(Mandatory=$true)] + [REF] + $PackageManagementProvider + ) + + $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary + $dynamicOptions = $null + + $loc = Get-LocationString -LocationUri $Location + + if(-not $loc) + { + return $paramDictionary + } + + # Ping and resolve the specified location + $loc = Resolve-Location -Location $loc ` + -LocationParameterName 'Location' ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + if(-not $loc) + { + return $paramDictionary + } + + $providers = PackageManagement\Get-PackageProvider | Where-Object { $_.Features.ContainsKey($script:SupportsPSModulesFeatureName) } + + if ($PackageManagementProvider.Value) + { + # Skip the PowerShellGet provider + if($PackageManagementProvider.Value -ne $script:PSModuleProviderName) + { + $SelectedProvider = $providers | Where-Object {$_.ProviderName -eq $PackageManagementProvider.Value} + + if($SelectedProvider) + { + $res = Get-PackageSource -Location $loc -Provider $PackageManagementProvider.Value -ErrorAction SilentlyContinue + + if($res) + { + $dynamicOptions = $SelectedProvider.DynamicOptions + } + } + } + } + else + { + $PackageManagementProvider.Value = Get-PackageManagementProviderName -Location $Location + if($PackageManagementProvider.Value) + { + $provider = $providers | Where-Object {$_.ProviderName -eq $PackageManagementProvider.Value} + $dynamicOptions = $provider.DynamicOptions + } + } + + foreach ($option in $dynamicOptions) + { + # Skip the Destination parameter + if( $option.IsRequired -and + ($option.Name -eq "Destination") ) + { + continue + } + + $paramAttribute = New-Object System.Management.Automation.ParameterAttribute + $paramAttribute.Mandatory = $option.IsRequired + + $message = $LocalizedData.DynamicParameterHelpMessage -f ($option.Name, $PackageManagementProvider.Value, $loc, $option.Name) + $paramAttribute.HelpMessage = $message + + $attributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute] + $attributeCollection.Add($paramAttribute) + + $ageParam = New-Object System.Management.Automation.RuntimeDefinedParameter($option.Name, + $script:DynamicOptionTypeMap[$option.Type.value__], + $attributeCollection) + $paramDictionary.Add($option.Name, $ageParam) + } + + return $paramDictionary +} + +function New-PSGetItemInfo +{ + param + ( + [Parameter(Mandatory=$true)] + $SoftwareIdentity, + + [Parameter()] + $PackageManagementProviderName, + + [Parameter()] + [string] + $SourceLocation, + + [Parameter(Mandatory=$true)] + [string] + $Type, + + [Parameter()] + [string] + $InstalledLocation, + + [Parameter()] + [System.DateTime] + $InstalledDate, + + [Parameter()] + [System.DateTime] + $UpdatedDate + ) + + foreach($swid in $SoftwareIdentity) + { + + if($SourceLocation) + { + $sourceName = (Get-SourceName -Location $SourceLocation) + } + else + { + # First get the source name from the Metadata + # if not exists, get the source name from $swid.Source + # otherwise default to $swid.Source + $sourceName = (Get-First $swid.Metadata["SourceName"]) + + if(-not $sourceName) + { + $sourceName = (Get-SourceName -Location $swid.Source) + } + + if(-not $sourceName) + { + $sourceName = $swid.Source + } + + $SourceLocation = Get-SourceLocation -SourceName $sourceName + } + + $published = (Get-First $swid.Metadata["published"]) + $PublishedDate = New-Object System.DateTime + + $InstalledDateString = (Get-First $swid.Metadata['installeddate']) + if(-not $InstalledDate -and $InstalledDateString) + { + $InstalledDate = New-Object System.DateTime + if(-not ([System.DateTime]::TryParse($InstalledDateString, ([ref]$InstalledDate)))) + { + $InstalledDate = $null + } + } + + $UpdatedDateString = (Get-First $swid.Metadata['updateddate']) + if(-not $UpdatedDate -and $UpdatedDateString) + { + $UpdatedDate = New-Object System.DateTime + if(-not ([System.DateTime]::TryParse($UpdatedDateString, ([ref]$UpdatedDate)))) + { + $UpdatedDate = $null + } + } + + $tags = (Get-First $swid.Metadata["tags"]) -split " " + $userTags = @() + + $exportedDscResources = @() + $exportedRoleCapabilities = @() + $exportedCmdlets = @() + $exportedFunctions = @() + $exportedWorkflows = @() + $exportedCommands = @() + + $exportedRoleCapabilities += (Get-First $swid.Metadata['RoleCapabilities']) -split " " | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + $exportedDscResources += (Get-First $swid.Metadata["DscResources"]) -split " " | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + $exportedCmdlets += (Get-First $swid.Metadata["Cmdlets"]) -split " " | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + $exportedFunctions += (Get-First $swid.Metadata["Functions"]) -split " " | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + $exportedWorkflows += (Get-First $swid.Metadata["Workflows"]) -split " " | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + $exportedCommands += $exportedCmdlets + $exportedFunctions + $exportedWorkflows + $PSGetFormatVersion = $null + + ForEach($tag in $tags) + { + if(-not $tag.Trim()) + { + continue + } + + $parts = $tag -split "_",2 + if($parts.Count -ne 2) + { + $userTags += $tag + continue + } + + Switch($parts[0]) + { + $script:Command { $exportedCommands += $parts[1]; break } + $script:DscResource { $exportedDscResources += $parts[1]; break } + $script:Cmdlet { $exportedCmdlets += $parts[1]; break } + $script:Function { $exportedFunctions += $parts[1]; break } + $script:Workflow { $exportedWorkflows += $parts[1]; break } + $script:RoleCapability { $exportedRoleCapabilities += $parts[1]; break } + $script:PSGetFormatVersion { $PSGetFormatVersion = $parts[1]; break } + $script:Includes { break } + Default { $userTags += $tag; break } + } + } + + $ArtifactDependencies = @() + Foreach ($dependencyString in $swid.Dependencies) + { + [Uri]$packageId = $null + if([Uri]::TryCreate($dependencyString, [System.UriKind]::Absolute, ([ref]$packageId))) + { + $segments = $packageId.Segments + $Version = $null + $DependencyName = $null + if ($segments) + { + $DependencyName = [Uri]::UnescapeDataString($segments[0].Trim('/', '\')) + $Version = if($segments.Count -gt 1){[Uri]::UnescapeDataString($segments[1])} + } + + $dep = [ordered]@{ + Name=$DependencyName + } + + if($Version) + { + # Required/exact version is represented in NuGet as "[2.0]" + if ($Version -match "\[+[0-9.]+\]") + { + $dep["RequiredVersion"] = $Version.Trim('[', ']') + } + elseif ($Version -match "\[+[0-9., ]+\]") + { + # Minimum and Maximum version range is represented in NuGet as "[1.0, 2.0]" + $versionRange = $Version.Trim('[', ']') -split ',' | Microsoft.PowerShell.Core\Where-Object {$_} + if($versionRange -and $versionRange.count -eq 2) + { + $dep["MinimumVersion"] = $versionRange[0].Trim() + $dep["MaximumVersion"] = $versionRange[1].Trim() + } + } + elseif ($Version -match "\(+[0-9., ]+\]") + { + # Maximum version is represented in NuGet as "(, 2.0]" + $maximumVersion = $Version.Trim('(', ']') -split ',' | Microsoft.PowerShell.Core\Where-Object {$_} + + if($maximumVersion) + { + $dep["MaximumVersion"] = $maximumVersion.Trim() + } + } + else + { + $dep['MinimumVersion'] = $Version + } + } + + $dep["CanonicalId"]=$dependencyString + + $ArtifactDependencies += $dep + } + } + + $additionalMetadata = New-Object -TypeName System.Collections.Hashtable + foreach ( $key in $swid.Metadata.Keys.LocalName) + { + if (!$additionalMetadata.ContainsKey($key)) + { + $additionalMetadata.Add($key, (Get-First $swid.Metadata[$key]) ) + } + } + + if($additionalMetadata.ContainsKey('ItemType')) + { + $Type = $additionalMetadata['ItemType'] + } + elseif($userTags -contains 'PSModule') + { + $Type = $script:PSArtifactTypeModule + } + elseif($userTags -contains 'PSScript') + { + $Type = $script:PSArtifactTypeScript + } + + $PSGetItemInfo = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + Name = $swid.Name + Version = [Version]$swid.Version + Type = $Type + Description = (Get-First $swid.Metadata["description"]) + Author = (Get-EntityName -SoftwareIdentity $swid -Role "author") + CompanyName = (Get-EntityName -SoftwareIdentity $swid -Role "owner") + Copyright = (Get-First $swid.Metadata["copyright"]) + PublishedDate = if([System.DateTime]::TryParse($published, ([ref]$PublishedDate))){$PublishedDate}; + InstalledDate = $InstalledDate; + UpdatedDate = $UpdatedDate; + LicenseUri = (Get-UrlFromSwid -SoftwareIdentity $swid -UrlName "license") + ProjectUri = (Get-UrlFromSwid -SoftwareIdentity $swid -UrlName "project") + IconUri = (Get-UrlFromSwid -SoftwareIdentity $swid -UrlName "icon") + Tags = $userTags + + Includes = @{ + DscResource = $exportedDscResources + Command = $exportedCommands + Cmdlet = $exportedCmdlets + Function = $exportedFunctions + Workflow = $exportedWorkflows + RoleCapability = $exportedRoleCapabilities + } + + PowerShellGetFormatVersion=[Version]$PSGetFormatVersion + + ReleaseNotes = (Get-First $swid.Metadata["releaseNotes"]) + + Dependencies = $ArtifactDependencies + + RepositorySourceLocation = $SourceLocation + Repository = $sourceName + PackageManagementProvider = if($PackageManagementProviderName) { $PackageManagementProviderName } else { (Get-First $swid.Metadata["PackageManagementProvider"]) } + + AdditionalMetadata = $additionalMetadata + }) + + if(-not $InstalledLocation) + { + $InstalledLocation = (Get-First $swid.Metadata[$script:InstalledLocation]) + } + + if($InstalledLocation) + { + Microsoft.PowerShell.Utility\Add-Member -InputObject $PSGetItemInfo -MemberType NoteProperty -Name $script:InstalledLocation -Value $InstalledLocation + } + + $PSGetItemInfo.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.PSRepositoryItemInfo") + $PSGetItemInfo + } +} + +function Get-SourceName +{ + [CmdletBinding()] + [OutputType("string")] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Location + ) + + Set-ModuleSourcesVariable + + foreach($psModuleSource in $script:PSGetModuleSources.Values) + { + if(($psModuleSource.Name -eq $Location) -or + ($psModuleSource.SourceLocation -eq $Location) -or + ((Get-Member -InputObject $psModuleSource -Name $script:ScriptSourceLocation) -and + ($psModuleSource.ScriptSourceLocation -eq $Location))) + { + return $psModuleSource.Name + } + } +} + +function Get-SourceLocation +{ + [CmdletBinding()] + [OutputType("string")] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $SourceName + ) + + Set-ModuleSourcesVariable + + if($script:PSGetModuleSources.Contains($SourceName)) + { + return $script:PSGetModuleSources[$SourceName].SourceLocation + } + else + { + return $SourceName + } +} + +function Get-UrlFromSwid +{ + param + ( + [Parameter(Mandatory=$true)] + $SoftwareIdentity, + + [Parameter(Mandatory=$true)] + $UrlName + ) + + foreach($link in $SoftwareIdentity.Links) + { + if( $link.Relationship -eq $UrlName) + { + return $link.HRef + } + } + + return $null +} + +function Get-EntityName +{ + param + ( + [Parameter(Mandatory=$true)] + $SoftwareIdentity, + + [Parameter(Mandatory=$true)] + $Role + ) + + foreach( $entity in $SoftwareIdentity.Entities ) + { + if( $entity.Role -eq $Role) + { + $entity.Name + } + } +} + +function Install-NuGetClientBinaries +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCmdlet] + $CallerPSCmdlet, + + [parameter()] + [switch] + $BootstrapNuGetExe, + + [parameter()] + [switch] + $Force + ) + + if($script:NuGetProvider -and + (-not $BootstrapNuGetExe -or + ($script:NuGetExePath -and (Microsoft.PowerShell.Management\Test-Path -Path $script:NuGetExePath)))) + { + return + } + + $bootstrapNuGetProvider = (-not $script:NuGetProvider) + + if($bootstrapNuGetProvider) + { + # Bootstrap the NuGet provider only if it is not available. + # By default PackageManagement loads the latest version of the NuGet provider. + $nugetProvider = PackageManagement\Get-PackageProvider -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | + Microsoft.PowerShell.Core\Where-Object { + $_.Name -eq $script:NuGetProviderName -and + $_.Version -ge $script:NuGetProviderVersion + } + if($nugetProvider) + { + $script:NuGetProvider = $nugetProvider + + $bootstrapNuGetProvider = $false + } + else + { + # User might have installed it in an another console or in the same process, check available NuGet providers and import the required provider. + $availableNugetProviders = PackageManagement\Get-PackageProvider -Name $script:NuGetProviderName ` + -ListAvailable ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue | + Microsoft.PowerShell.Core\Where-Object { + $_.Name -eq $script:NuGetProviderName -and + $_.Version -ge $script:NuGetProviderVersion + } + if($availableNugetProviders) + { + # Force import ensures that nuget provider with minimum version got loaded. + $null = PackageManagement\Import-PackageProvider -Name $script:NuGetProviderName ` + -MinimumVersion $script:NuGetProviderVersion ` + -Force + + $nugetProvider = PackageManagement\Get-PackageProvider -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | + Microsoft.PowerShell.Core\Where-Object { + $_.Name -eq $script:NuGetProviderName -and + $_.Version -ge $script:NuGetProviderVersion + } + if($nugetProvider) + { + $script:NuGetProvider = $nugetProvider + + $bootstrapNuGetProvider = $false + } + } + } + } + + if($BootstrapNuGetExe -and + (-not $script:NuGetExePath -or + -not (Microsoft.PowerShell.Management\Test-Path -Path $script:NuGetExePath))) + { + $programDataExePath = Microsoft.PowerShell.Management\Join-Path -Path $script:PSGetProgramDataPath -ChildPath $script:NuGetExeName + $applocalDataExePath = Microsoft.PowerShell.Management\Join-Path -Path $script:PSGetAppLocalPath -ChildPath $script:NuGetExeName + + # Check if NuGet.exe is available under one of the predefined PowerShellGet locations under ProgramData or LocalAppData + if(Microsoft.PowerShell.Management\Test-Path -Path $programDataExePath) + { + $script:NuGetExePath = $programDataExePath + $BootstrapNuGetExe = $false + } + elseif(Microsoft.PowerShell.Management\Test-Path -Path $applocalDataExePath) + { + $script:NuGetExePath = $applocalDataExePath + $BootstrapNuGetExe = $false + } + else + { + # Using Get-Command cmdlet, get the location of NuGet.exe if it is available under $env:PATH. + # NuGet.exe does not work if it is under $env:WINDIR, so skip it from the Get-Command results. + $nugetCmd = Microsoft.PowerShell.Core\Get-Command -Name $script:NuGetExeName ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue | + Microsoft.PowerShell.Core\Where-Object { + $_.Path -and + ((Microsoft.PowerShell.Management\Split-Path -Path $_.Path -Leaf) -eq $script:NuGetExeName) -and + (-not $_.Path.StartsWith($env:windir, [System.StringComparison]::OrdinalIgnoreCase)) + } | Microsoft.PowerShell.Utility\Select-Object -First 1 + + if($nugetCmd -and $nugetCmd.Path) + { + $script:NuGetExePath = $nugetCmd.Path + $BootstrapNuGetExe = $false + } + } + } + else + { + # No need to bootstrap the NuGet.exe when $BootstrapNuGetExe is false or NuGet.exe path is already assigned. + $BootstrapNuGetExe = $false + } + + # On Nano server we don't need NuGet.exe + if(-not $bootstrapNuGetProvider -and ($script:isNanoServer -or -not $BootstrapNuGetExe)) + { + return + } + + # We should prompt only once for bootstrapping the NuGet provider and/or NuGet.exe + + # Should continue message for bootstrapping only NuGet provider + $shouldContinueQueryMessage = $LocalizedData.InstallNuGetProviderShouldContinueQuery -f @($script:NuGetProviderVersion,$script:NuGetBinaryProgramDataPath,$script:NuGetBinaryLocalAppDataPath) + $shouldContinueCaption = $LocalizedData.InstallNuGetProviderShouldContinueCaption + + # Should continue message for bootstrapping both NuGet provider and NuGet.exe + if($bootstrapNuGetProvider -and $BootstrapNuGetExe) + { + $shouldContinueQueryMessage = $LocalizedData.InstallNuGetBinariesShouldContinueQuery2 -f @($script:NuGetProviderVersion,$script:NuGetBinaryProgramDataPath,$script:NuGetBinaryLocalAppDataPath, $script:PSGetProgramDataPath, $script:PSGetAppLocalPath) + $shouldContinueCaption = $LocalizedData.InstallNuGetBinariesShouldContinueCaption2 + } + elseif($BootstrapNuGetExe) + { + # Should continue message for bootstrapping only NuGet.exe + $shouldContinueQueryMessage = $LocalizedData.InstallNuGetExeShouldContinueQuery -f ($script:PSGetProgramDataPath, $script:PSGetAppLocalPath) + $shouldContinueCaption = $LocalizedData.InstallNuGetExeShouldContinueCaption + } + + if($Force -or $psCmdlet.ShouldContinue($shouldContinueQueryMessage, $shouldContinueCaption)) + { + if($bootstrapNuGetProvider) + { + Write-Verbose -Message $LocalizedData.DownloadingNugetProvider + + $scope = 'CurrentUser' + if(Test-RunningAsElevated) + { + $scope = 'AllUsers' + } + + # Bootstrap the NuGet provider + $null = PackageManagement\Install-PackageProvider -Name $script:NuGetProviderName ` + -MinimumVersion $script:NuGetProviderVersion ` + -Scope $scope ` + -Force + + # Force import ensures that nuget provider with minimum version got loaded. + $null = PackageManagement\Import-PackageProvider -Name $script:NuGetProviderName ` + -MinimumVersion $script:NuGetProviderVersion ` + -Force + + $nugetProvider = PackageManagement\Get-PackageProvider -Name $script:NuGetProviderName + + if ($nugetProvider) + { + $script:NuGetProvider = $nugetProvider + } + } + + if($BootstrapNuGetExe -and -not $script:isNanoServer) + { + Write-Verbose -Message $LocalizedData.DownloadingNugetExe + + $nugetExeBasePath = $script:PSGetAppLocalPath + + # if the current process is running with elevated privileges, + # install NuGet.exe to $script:PSGetProgramDataPath + if(Test-RunningAsElevated) + { + $nugetExeBasePath = $script:PSGetProgramDataPath + } + + if(-not (Microsoft.PowerShell.Management\Test-Path -Path $nugetExeBasePath)) + { + $null = Microsoft.PowerShell.Management\New-Item -Path $nugetExeBasePath ` + -ItemType Directory -Force ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false -WhatIf:$false + } + + $nugetExeFilePath = Microsoft.PowerShell.Management\Join-Path -Path $nugetExeBasePath -ChildPath $script:NuGetExeName + + # Download the NuGet.exe from http://nuget.org/NuGet.exe + $null = Microsoft.PowerShell.Utility\Invoke-WebRequest -Uri $script:NuGetClientSourceURL ` + -OutFile $nugetExeFilePath + + if (Microsoft.PowerShell.Management\Test-Path -Path $nugetExeFilePath) + { + $script:NuGetExePath = $nugetExeFilePath + } + } + } + + $message = $null + $errorId = $null + $failedToBootstrapNuGetProvider = $false + $failedToBootstrapNuGetExe = $false + + if($bootstrapNuGetProvider -and -not $script:NuGetProvider) + { + $failedToBootstrapNuGetProvider = $true + + $message = $LocalizedData.CouldNotInstallNuGetProvider -f @($script:NuGetProviderVersion) + $errorId = 'CouldNotInstallNuGetProvider' + } + + if($BootstrapNuGetExe -and + (-not $script:NuGetExePath -or + -not (Microsoft.PowerShell.Management\Test-Path -Path $script:NuGetExePath))) + { + $failedToBootstrapNuGetExe = $true + + $message = $LocalizedData.CouldNotInstallNuGetExe -f @($script:NuGetProviderVersion) + $errorId = 'CouldNotInstallNuGetExe' + } + + # Change the error id and message if both NuGet provider and NuGet.exe are not installed. + if($failedToBootstrapNuGetProvider -and $failedToBootstrapNuGetExe) + { + $message = $LocalizedData.CouldNotInstallNuGetBinaries2 -f @($script:NuGetProviderVersion) + $errorId = 'CouldNotInstallNuGetBinaries' + } + + # Throw the error message if one of the above conditions are met + if($message -and $errorId) + { + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId $errorId ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidOperation + } +} + +# Check if current user is running with elevated privileges +function Test-RunningAsElevated +{ + [CmdletBinding()] + [OutputType([bool])] + Param() + + $wid=[System.Security.Principal.WindowsIdentity]::GetCurrent() + $prp=new-object System.Security.Principal.WindowsPrincipal($wid) + $adm=[System.Security.Principal.WindowsBuiltInRole]::Administrator + return $prp.IsInRole($adm) +} + +function Get-EscapedString +{ + [CmdletBinding()] + [OutputType([String])] + Param + ( + [Parameter()] + [string] + $ElementValue + ) + + return [System.Security.SecurityElement]::Escape($ElementValue) +} + +function ValidateAndGet-ScriptDependencies +{ + param( + [Parameter(Mandatory=$true)] + [string] + $Repository, + + [Parameter(Mandatory=$true)] + [PSCustomObject] + $DependentScriptInfo, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCmdlet] + $CallerPSCmdlet + ) + + $DependenciesDetails = @() + + # Validate dependent modules + $RequiredModuleSpecification = $DependentScriptInfo.RequiredModules + if($RequiredModuleSpecification) + { + ForEach($moduleSpecification in $RequiredModuleSpecification) + { + $ModuleName = $moduleSpecification.Name + + $FindModuleArguments = @{ + Repository = $Repository + Verbose = $VerbosePreference + ErrorAction = 'SilentlyContinue' + WarningAction = 'SilentlyContinue' + Debug = $DebugPreference + } + + if($DependentScriptInfo.ExternalModuleDependencies -contains $ModuleName) + { + Write-Verbose -Message ($LocalizedData.SkippedModuleDependency -f $ModuleName) + + continue + } + + $FindModuleArguments['Name'] = $ModuleName + $ReqModuleInfo = @{} + $ReqModuleInfo['Name'] = $ModuleName + + if($moduleSpecification.Version) + { + $FindModuleArguments['MinimumVersion'] = $moduleSpecification.Version + $ReqModuleInfo['MinimumVersion'] = $moduleSpecification.Version + } + elseif((Get-Member -InputObject $moduleSpecification -Name RequiredVersion) -and $moduleSpecification.RequiredVersion) + { + $FindModuleArguments['RequiredVersion'] = $moduleSpecification.RequiredVersion + $ReqModuleInfo['RequiredVersion'] = $moduleSpecification.RequiredVersion + } + + if((Get-Member -InputObject $moduleSpecification -Name MaximumVersion) -and $moduleSpecification.MaximumVersion) + { + # * can be specified in the MaximumVersion of a ModuleSpecification to convey that maximum possible value of that version part. + # like 1.0.0.* --> 1.0.0.99999999 + # replace * with 99999999, PowerShell core takes care validating the * to be the last character in the version string. + $maximumVersion = $moduleSpecification.MaximumVersion -replace '\*','99999999' + $FindModuleArguments['MaximumVersion'] = $maximumVersion + $ReqModuleInfo['MaximumVersion'] = $maximumVersion + } + + $psgetItemInfo = Find-Module @FindModuleArguments | + Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $ModuleName} | + Microsoft.PowerShell.Utility\Select-Object -Last 1 + + if(-not $psgetItemInfo) + { + $message = $LocalizedData.UnableToResolveScriptDependency -f ('module', $ModuleName, $DependentScriptInfo.Name, $Repository, 'ExternalModuleDependencies') + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "UnableToResolveScriptDependency" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidOperation + } + + $DependenciesDetails += $ReqModuleInfo + } + } + + # Validate dependent scrips + $RequiredScripts = $DependentScriptInfo.RequiredScripts + if($RequiredScripts) + { + ForEach($requiredScript in $RequiredScripts) + { + $FindScriptArguments = @{ + Repository = $Repository + Verbose = $VerbosePreference + ErrorAction = 'SilentlyContinue' + WarningAction = 'SilentlyContinue' + Debug = $DebugPreference + } + + if($DependentScriptInfo.ExternalScriptDependencies -contains $requiredScript) + { + Write-Verbose -Message ($LocalizedData.SkippedScriptDependency -f $requiredScript) + + continue + } + + $FindScriptArguments['Name'] = $requiredScript + $ReqScriptInfo = @{} + $ReqScriptInfo['Name'] = $requiredScript + + $psgetItemInfo = Find-Script @FindScriptArguments | + Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $requiredScript} | + Microsoft.PowerShell.Utility\Select-Object -Last 1 + + if(-not $psgetItemInfo) + { + $message = $LocalizedData.UnableToResolveScriptDependency -f ('script', $requiredScript, $DependentScriptInfo.Name, $Repository, 'ExternalScriptDependencies') + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "UnableToResolveScriptDependency" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidOperation + } + + $DependenciesDetails += $ReqScriptInfo + } + } + + return $DependenciesDetails +} + +function ValidateAndGet-RequiredModuleDetails +{ + param( + [Parameter()] + $ModuleManifestRequiredModules, + + [Parameter()] + [PSModuleInfo[]] + $RequiredPSModuleInfos, + + [Parameter(Mandatory=$true)] + [string] + $Repository, + + [Parameter(Mandatory=$true)] + [PSModuleInfo] + $DependentModuleInfo, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCmdlet] + $CallerPSCmdlet + ) + + $RequiredModuleDetails = @() + + if(-not $RequiredPSModuleInfos) + { + return $RequiredModuleDetails + } + + if($ModuleManifestRequiredModules) + { + ForEach($RequiredModule in $ModuleManifestRequiredModules) + { + $ModuleName = $null + $VersionString = $null + + $ReqModuleInfo = @{} + + $FindModuleArguments = @{ + Repository = $Repository + Verbose = $VerbosePreference + ErrorAction = 'SilentlyContinue' + WarningAction = 'SilentlyContinue' + Debug = $DebugPreference + } + + # ModuleSpecification case + if($RequiredModule.GetType().ToString() -eq 'System.Collections.Hashtable') + { + $ModuleName = $RequiredModule.ModuleName + + # Version format in NuSpec: + # "[2.0]" --> (== 2.0) Required Version + # "2.0" --> (>= 2.0) Minimum Version + if($RequiredModule.Keys -Contains "RequiredVersion") + { + $FindModuleArguments['RequiredVersion'] = $RequiredModule.RequiredVersion + $ReqModuleInfo['RequiredVersion'] = $RequiredModule.RequiredVersion + } + elseif($RequiredModule.Keys -Contains "ModuleVersion") + { + $FindModuleArguments['MinimumVersion'] = $RequiredModule.ModuleVersion + $ReqModuleInfo['MinimumVersion'] = $RequiredModule.ModuleVersion + } + + if($RequiredModule.Keys -Contains 'MaximumVersion' -and $RequiredModule.MaximumVersion) + { + # * can be specified in the MaximumVersion of a ModuleSpecification to convey that maximum possible value of that version part. + # like 1.0.0.* --> 1.0.0.99999999 + # replace * with 99999999, PowerShell core takes care validating the * to be the last character in the version string. + $maximumVersion = $RequiredModule.MaximumVersion -replace '\*','99999999' + + $FindModuleArguments['MaximumVersion'] = $maximumVersion + $ReqModuleInfo['MaximumVersion'] = $maximumVersion + } + } + else + { + # Just module name was specified + $ModuleName = $RequiredModule.ToString() + } + + if((Get-ExternalModuleDependencies -PSModuleInfo $DependentModuleInfo) -contains $ModuleName) + { + Write-Verbose -Message ($LocalizedData.SkippedModuleDependency -f $ModuleName) + + continue + } + + # Skip this module name if it's name is not in $RequiredPSModuleInfos. + # This is required when a ModuleName is part of the NestedModules list of the actual module. + # $ModuleName is packaged as part of the actual module When $RequiredPSModuleInfos doesn't contain it's name. + if($RequiredPSModuleInfos.Name -notcontains $ModuleName) + { + continue + } + + $ReqModuleInfo['Name'] = $ModuleName + + # Add the dependency only if the module is available on the gallery + # Otherwise Module installation will fail as all required modules need to be available on + # the same Repository + $FindModuleArguments['Name'] = $ModuleName + + $psgetItemInfo = Find-Module @FindModuleArguments | + Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $ModuleName} | + Microsoft.PowerShell.Utility\Select-Object -Last 1 + + if(-not $psgetItemInfo) + { + $message = $LocalizedData.UnableToResolveModuleDependency -f ($ModuleName, $DependentModuleInfo.Name, $Repository, $ModuleName, $Repository, $ModuleName, $ModuleName) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "UnableToResolveModuleDependency" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidOperation + } + + $RequiredModuleDetails += $ReqModuleInfo + } + } + else + { + # If Import-LocalizedData cmdlet was failed to read the .psd1 contents + # use provided $RequiredPSModuleInfos (PSModuleInfo.RequiredModules or PSModuleInfo.NestedModules of the actual dependent module) + + $FindModuleArguments = @{ + Repository = $Repository + Verbose = $VerbosePreference + ErrorAction = 'SilentlyContinue' + WarningAction = 'SilentlyContinue' + Debug = $DebugPreference + } + + ForEach($RequiredModuleInfo in $RequiredPSModuleInfos) + { + $ModuleName = $requiredModuleInfo.Name + + if((Get-ExternalModuleDependencies -PSModuleInfo $DependentModuleInfo) -contains $ModuleName) + { + Write-Verbose -Message ($LocalizedData.SkippedModuleDependency -f $ModuleName) + + continue + } + + $FindModuleArguments['Name'] = $ModuleName + $FindModuleArguments['MinimumVersion'] = $requiredModuleInfo.Version + + $psgetItemInfo = Find-Module @FindModuleArguments | + Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $ModuleName} | + Microsoft.PowerShell.Utility\Select-Object -Last 1 + + if(-not $psgetItemInfo) + { + $message = $LocalizedData.UnableToResolveModuleDependency -f ($ModuleName, $DependentModuleInfo.Name, $Repository, $ModuleName, $Repository, $ModuleName, $ModuleName) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "UnableToResolveModuleDependency" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + } + + $RequiredModuleDetails += @{ + Name=$_.Name + MinimumVersion=$_.Version + } + } + } + + return $RequiredModuleDetails +} + +function Get-ExternalModuleDependencies +{ + Param ( + [Parameter(Mandatory=$true)] + [PSModuleInfo] + $PSModuleInfo + ) + + if($PSModuleInfo.PrivateData -and + ($PSModuleInfo.PrivateData.GetType().ToString() -eq "System.Collections.Hashtable") -and + $PSModuleInfo.PrivateData["PSData"] -and + ($PSModuleInfo.PrivateData["PSData"].GetType().ToString() -eq "System.Collections.Hashtable") -and + $PSModuleInfo.PrivateData.PSData['ExternalModuleDependencies'] -and + ($PSModuleInfo.PrivateData.PSData['ExternalModuleDependencies'].GetType().ToString() -eq "System.Object[]") + ) + { + return $PSModuleInfo.PrivateData.PSData.ExternalModuleDependencies + } +} + +function Get-ModuleDependencies +{ + Param ( + [Parameter(Mandatory=$true)] + [PSModuleInfo] + $PSModuleInfo, + + [Parameter(Mandatory=$true)] + [string] + $Repository, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCmdlet] + $CallerPSCmdlet + ) + + $DependentModuleDetails = @() + + if($PSModuleInfo.RequiredModules -or $PSModuleInfo.NestedModules) + { + # PSModuleInfo.RequiredModules doesn't provide the RequiredVersion info from the ModuleSpecification + # Reading the contents of module manifest file using Import-LocalizedData cmdlet + # to get the RequiredVersion details. + Import-LocalizedData -BindingVariable ModuleManifestHashTable ` + -FileName (Microsoft.PowerShell.Management\Split-Path $PSModuleInfo.Path -Leaf) ` + -BaseDirectory $PSModuleInfo.ModuleBase ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + + if($PSModuleInfo.RequiredModules) + { + $ModuleManifestRequiredModules = $null + + if($ModuleManifestHashTable) + { + $ModuleManifestRequiredModules = $ModuleManifestHashTable.RequiredModules + } + + + $DependentModuleDetails += ValidateAndGet-RequiredModuleDetails -ModuleManifestRequiredModules $ModuleManifestRequiredModules ` + -RequiredPSModuleInfos $PSModuleInfo.RequiredModules ` + -Repository $Repository ` + -DependentModuleInfo $PSModuleInfo ` + -CallerPSCmdlet $CallerPSCmdlet ` + -Verbose:$VerbosePreference ` + -Debug:$DebugPreference + } + + if($PSModuleInfo.NestedModules) + { + $ModuleManifestRequiredModules = $null + + if($ModuleManifestHashTable) + { + $ModuleManifestRequiredModules = $ModuleManifestHashTable.NestedModules + } + + # A nested module is be considered as a dependency + # 1) whose module base is not under the specified module base OR + # 2) whose module base is under the specified module base and it's path doesn't exists + # + $RequiredPSModuleInfos = $PSModuleInfo.NestedModules | Microsoft.PowerShell.Core\Where-Object { + -not $_.ModuleBase.StartsWith($PSModuleInfo.ModuleBase, [System.StringComparison]::OrdinalIgnoreCase) -or + -not $_.Path -or + -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $_.Path) + } + + $DependentModuleDetails += ValidateAndGet-RequiredModuleDetails -ModuleManifestRequiredModules $ModuleManifestRequiredModules ` + -RequiredPSModuleInfos $RequiredPSModuleInfos ` + -Repository $Repository ` + -DependentModuleInfo $PSModuleInfo ` + -CallerPSCmdlet $CallerPSCmdlet ` + -Verbose:$VerbosePreference ` + -Debug:$DebugPreference + } + } + + return $DependentModuleDetails +} + +function Publish-PSArtifactUtility +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true, ParameterSetName='PublishModule')] + [ValidateNotNullOrEmpty()] + [PSModuleInfo] + $PSModuleInfo, + + [Parameter(Mandatory=$true, ParameterSetName='PublishScript')] + [ValidateNotNullOrEmpty()] + [PSCustomObject] + $PSScriptInfo, + + [Parameter(Mandatory=$true, ParameterSetName='PublishModule')] + [ValidateNotNullOrEmpty()] + [string] + $ManifestPath, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Destination, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Repository, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $NugetApiKey, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $NugetPackageRoot, + + [Parameter(ParameterSetName='PublishModule')] + [Version] + $FormatVersion, + + [Parameter(ParameterSetName='PublishModule')] + [string] + $ReleaseNotes, + + [Parameter(ParameterSetName='PublishModule')] + [string[]] + $Tags, + + [Parameter(ParameterSetName='PublishModule')] + [Uri] + $LicenseUri, + + [Parameter(ParameterSetName='PublishModule')] + [Uri] + $IconUri, + + [Parameter(ParameterSetName='PublishModule')] + [Uri] + $ProjectUri + ) + + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -BootstrapNuGetExe + + $PSArtifactType = $script:PSArtifactTypeModule + $Name = $null + $Description = $null + $Version = $null + $Author = $null + $CompanyName = $null + $Copyright = $null + + if($PSModuleInfo) + { + $Name = $PSModuleInfo.Name + $Description = $PSModuleInfo.Description + $Version = $PSModuleInfo.Version + $Author = $PSModuleInfo.Author + $CompanyName = $PSModuleInfo.CompanyName + $Copyright = $PSModuleInfo.Copyright + + if($PSModuleInfo.PrivateData -and + ($PSModuleInfo.PrivateData.GetType().ToString() -eq "System.Collections.Hashtable") -and + $PSModuleInfo.PrivateData["PSData"] -and + ($PSModuleInfo.PrivateData["PSData"].GetType().ToString() -eq "System.Collections.Hashtable") + ) + { + if( -not $Tags -and $PSModuleInfo.PrivateData.PSData["Tags"]) + { + $Tags = $PSModuleInfo.PrivateData.PSData.Tags + } + + if( -not $ReleaseNotes -and $PSModuleInfo.PrivateData.PSData["ReleaseNotes"]) + { + $ReleaseNotes = $PSModuleInfo.PrivateData.PSData.ReleaseNotes + } + + if( -not $LicenseUri -and $PSModuleInfo.PrivateData.PSData["LicenseUri"]) + { + $LicenseUri = $PSModuleInfo.PrivateData.PSData.LicenseUri + } + + if( -not $IconUri -and $PSModuleInfo.PrivateData.PSData["IconUri"]) + { + $IconUri = $PSModuleInfo.PrivateData.PSData.IconUri + } + + if( -not $ProjectUri -and $PSModuleInfo.PrivateData.PSData["ProjectUri"]) + { + $ProjectUri = $PSModuleInfo.PrivateData.PSData.ProjectUri + } + } + } + else + { + $PSArtifactType = $script:PSArtifactTypeScript + + $Name = $PSScriptInfo.Name + $Description = $PSScriptInfo.Description + $Version = $PSScriptInfo.Version + $Author = $PSScriptInfo.Author + $CompanyName = $PSScriptInfo.CompanyName + $Copyright = $PSScriptInfo.Copyright + + if($PSScriptInfo.'Tags') + { + $Tags = $PSScriptInfo.Tags + } + + if($PSScriptInfo.'ReleaseNotes') + { + $ReleaseNotes = $PSScriptInfo.ReleaseNotes + } + + if($PSScriptInfo.'LicenseUri') + { + $LicenseUri = $PSScriptInfo.LicenseUri + } + + if($PSScriptInfo.'IconUri') + { + $IconUri = $PSScriptInfo.IconUri + } + + if($PSScriptInfo.'ProjectUri') + { + $ProjectUri = $PSScriptInfo.ProjectUri + } + } + + + # Add PSModule and PSGet format version tags + if(-not $Tags) + { + $Tags = @() + } + + if($FormatVersion) + { + $Tags += "$($script:PSGetFormatVersion)_$FormatVersion" + } + + $DependentModuleDetails = @() + + if($PSScriptInfo) + { + $Tags += "PSScript" + + if($PSScriptInfo.DefinedCommands) + { + if($PSScriptInfo.DefinedFunctions) + { + $Tags += "$($script:Includes)_Function" + $Tags += $PSScriptInfo.DefinedFunctions | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Function)_$_" } + } + + if($PSScriptInfo.DefinedWorkflows) + { + $Tags += "$($script:Includes)_Workflow" + $Tags += $PSScriptInfo.DefinedWorkflows | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Workflow)_$_" } + } + + $Tags += $PSScriptInfo.DefinedCommands | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Command)_$_" } + } + + # Populate the dependencies elements from RequiredModules and RequiredScripts + # + $DependentModuleDetails += ValidateAndGet-ScriptDependencies -Repository $Repository ` + -DependentScriptInfo $PSScriptInfo ` + -CallerPSCmdlet $PSCmdlet ` + -Verbose:$VerbosePreference ` + -Debug:$DebugPreference + } + else + { + $Tags += "PSModule" + + Import-LocalizedData -BindingVariable ModuleManifestHashTable ` + -FileName (Microsoft.PowerShell.Management\Split-Path $ManifestPath -Leaf) ` + -BaseDirectory (Microsoft.PowerShell.Management\Split-Path $ManifestPath -Parent) ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + + + if($PSModuleInfo.ExportedCommands.Count) + { + if($PSModuleInfo.ExportedCmdlets.Count) + { + $Tags += "$($script:Includes)_Cmdlet" + $Tags += $PSModuleInfo.ExportedCmdlets.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Cmdlet)_$_" } + + #if CmdletsToExport field in manifest file is "*", we suggest the user to include all those cmdlets for best practice + if($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey('CmdletsToExport') -and ($ModuleManifestHashTable.CmdletsToExport -eq "*")) + { + $WarningMessage = $LocalizedData.ShouldIncludeCmdletsToExport -f ($ManifestPath) + Write-Warning -Message $WarningMessage + } + } + + if($PSModuleInfo.ExportedFunctions.Count) + { + $Tags += "$($script:Includes)_Function" + $Tags += $PSModuleInfo.ExportedFunctions.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Function)_$_" } + + if($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey('FunctionsToExport') -and ($ModuleManifestHashTable.FunctionsToExport -eq "*")) + { + $WarningMessage = $LocalizedData.ShouldIncludeFunctionsToExport -f ($ManifestPath) + Write-Warning -Message $WarningMessage + } + } + + $Tags += $PSModuleInfo.ExportedCommands.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Command)_$_" } + } + + $dscResourceNames = Get-ExportedDscResources -PSModuleInfo $PSModuleInfo + if($dscResourceNames) + { + $Tags += "$($script:Includes)_DscResource" + + $Tags += $dscResourceNames | Microsoft.PowerShell.Core\ForEach-Object { "$($script:DscResource)_$_" } + + #If DscResourcesToExport is commented out or "*" is used, we will write-warning + if($ModuleManifestHashTable -and + ($ModuleManifestHashTable.ContainsKey("DscResourcesToExport") -and + $ModuleManifestHashTable.DscResourcesToExport -eq "*") -or + -not $ModuleManifestHashTable.ContainsKey("DscResourcesToExport")) + { + $WarningMessage = $LocalizedData.ShouldIncludeDscResourcesToExport -f ($ManifestPath) + Write-Warning -Message $WarningMessage + } + } + + $RoleCapabilityNames = Get-AvailableRoleCapabilityName -PSModuleInfo $PSModuleInfo + if($RoleCapabilityNames) + { + $Tags += "$($script:Includes)_RoleCapability" + + $Tags += $RoleCapabilityNames | Microsoft.PowerShell.Core\ForEach-Object { "$($script:RoleCapability)_$_" } + } + + # Populate the module dependencies elements from RequiredModules and + # NestedModules properties of the current PSModuleInfo + $DependentModuleDetails = Get-ModuleDependencies -PSModuleInfo $PSModuleInfo ` + -Repository $Repository ` + -CallerPSCmdlet $PSCmdlet ` + -Verbose:$VerbosePreference ` + -Debug:$DebugPreference + } + + $dependencies = @() + ForEach($Dependency in $DependentModuleDetails) + { + $ModuleName = $Dependency.Name + $VersionString = $null + + # Version format in NuSpec: + # "[2.0]" --> (== 2.0) Required Version + # "2.0" --> (>= 2.0) Minimum Version + # + # When only MaximumVersion is specified in the ModuleSpecification + # (,1.0] = x <= 1.0 + # + # When both Minimum and Maximum versions are specified in the ModuleSpecification + # [1.0,2.0] = 1.0 <= x <= 2.0 + + if($Dependency.Keys -Contains "RequiredVersion") + { + $VersionString = "[$($Dependency.RequiredVersion)]" + } + elseif($Dependency.Keys -Contains 'MinimumVersion' -and $Dependency.Keys -Contains 'MaximumVersion') + { + $VersionString = "[$($Dependency.MinimumVersion),$($Dependency.MaximumVersion)]" + } + elseif($Dependency.Keys -Contains 'MaximumVersion') + { + $VersionString = "(,$($Dependency.MaximumVersion)]" + } + elseif($Dependency.Keys -Contains 'MinimumVersion') + { + $VersionString = "$($Dependency.MinimumVersion)" + } + + $dependencies += "" + } + + # Populate the nuspec elements + $nuspec = @" + + + + $(Get-EscapedString -ElementValue "$Name") + $($Version) + $(Get-EscapedString -ElementValue "$Author") + $(Get-EscapedString -ElementValue "$CompanyName") + $(Get-EscapedString -ElementValue "$Description") + $(Get-EscapedString -ElementValue "$ReleaseNotes") + $(Get-EscapedString -ElementValue "$Copyright") + $(if($Tags){ Get-EscapedString -ElementValue ($Tags -join ' ')}) + $(if($LicenseUri){ + "$(Get-EscapedString -ElementValue "$LicenseUri") + true" + }) + $(if($ProjectUri){ + "$(Get-EscapedString -ElementValue "$ProjectUri")" + }) + $(if($IconUri){ + "$(Get-EscapedString -ElementValue "$IconUri")" + }) + + $dependencies + + + +"@ + + $NupkgPath = "$NugetPackageRoot\$Name.$($Version.ToString()).nupkg" + $NuspecPath = "$NugetPackageRoot\$Name.nuspec" + $tempErrorFile = $null + $tempOutputFile = $null + + try + { + # Remove existing nuspec and nupkg files + Microsoft.PowerShell.Management\Remove-Item $NupkgPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + Microsoft.PowerShell.Management\Remove-Item $NuspecPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + + Microsoft.PowerShell.Management\Set-Content -Value $nuspec -Path $NuspecPath + + # Create .nupkg file + $output = & $script:NuGetExePath pack $NuspecPath -OutputDirectory $NugetPackageRoot + if($LASTEXITCODE) + { + if($PSArtifactType -eq $script:PSArtifactTypeModule) + { + $message = $LocalizedData.FailedToCreateCompressedModule -f ($output) + $errorId = "FailedToCreateCompressedModule" + } + else + { + $message = $LocalizedData.FailedToCreateCompressedScript -f ($output) + $errorId = "FailedToCreateCompressedScript" + } + + Write-Error -Message $message -ErrorId $errorId -Category InvalidOperation + return + } + + # Publish the .nupkg to gallery + $tempErrorFile = Microsoft.PowerShell.Management\Join-Path -Path $nugetPackageRoot -ChildPath "TempPublishError.txt" + $tempOutputFile = Microsoft.PowerShell.Management\Join-Path -Path $nugetPackageRoot -ChildPath "TempPublishOutput.txt" + + Microsoft.PowerShell.Management\Start-Process -FilePath "$script:NuGetExePath" ` + -ArgumentList @('push', $NupkgPath, '-source', $Destination, '-NonInteractive', '-ApiKey', $NugetApiKey) ` + -RedirectStandardError $tempErrorFile ` + -RedirectStandardOutput $tempOutputFile ` + -NoNewWindow ` + -Wait + + $errorMsg = Microsoft.PowerShell.Management\Get-Content -Path $tempErrorFile -Raw + + if($errorMsg) + { + if($PSArtifactType -eq $script:PSArtifactTypeModule) + { + $message = $LocalizedData.FailedToPublish -f ($Name,$errorMsg) + $errorId = "FailedToPublishTheModule" + } + else + { + $message = $LocalizedData.FailedToPublishScript -f ($Name,$errorMsg) + $errorId = "FailedToPublishTheScript" + } + + Write-Error -Message $message -ErrorId $errorId -Category InvalidOperation + } + else + { + if($PSArtifactType -eq $script:PSArtifactTypeModule) + { + $message = $LocalizedData.PublishedSuccessfully -f ($Name, $Destination, $Name) + } + else + { + $message = $LocalizedData.PublishedScriptSuccessfully -f ($Name, $Destination, $Name) + } + + Write-Verbose -Message $message + } + } + finally + { + if($NupkgPath -and (Test-Path -Path $NupkgPath -PathType Leaf)) + { + Microsoft.PowerShell.Management\Remove-Item $NupkgPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } + + if($NuspecPath -and (Test-Path -Path $NuspecPath -PathType Leaf)) + { + Microsoft.PowerShell.Management\Remove-Item $NuspecPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } + + if($tempErrorFile -and (Test-Path -Path $tempErrorFile -PathType Leaf)) + { + Microsoft.PowerShell.Management\Remove-Item $tempErrorFile -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } + + if($tempOutputFile -and (Test-Path -Path $tempOutputFile -PathType Leaf)) + { + Microsoft.PowerShell.Management\Remove-Item $tempOutputFile -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } + } +} + +function ValidateAndAdd-PSScriptInfoEntry +{ + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$true)] + [PSCustomObject] + $PSScriptInfo, + + [Parameter(Mandatory=$true)] + [string] + $PropertyName, + + [Parameter()] + $PropertyValue, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCmdlet] + $CallerPSCmdlet + ) + + $Value = $PropertyValue + $KeyName = $PropertyName + + # return if $KeyName value is not null in $PSScriptInfo + if(-not $value -or -not $KeyName -or (Get-Member -InputObject $PSScriptInfo -Name $KeyName) -and $PSScriptInfo."$KeyName") + { + return + } + + switch($PropertyName) + { + # Validate the property value and also use proper key name as users can specify the property name in any case. + $script:Version { + $KeyName = $script:Version + + [Version]$Version = $null + + if([System.Version]::TryParse($Value, ([ref]$Version))) + { + $Value = $Version + } + else + { + $message = $LocalizedData.InvalidVersion -f ($Value) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidVersion" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Value + return + } + break + } + + $script:Author { $KeyName = $script:Author } + + $script:Guid { + $KeyName = $script:Guid + + [Guid]$guid = [System.Guid]::Empty + if([System.Guid]::TryParse($Value, ([ref]$guid))) + { + $Value = $guid + } + else + { + $message = $LocalizedData.InvalidGuid -f ($Value) + ThrowError -ExceptionName 'System.ArgumentException' ` + -ExceptionMessage $message ` + -ErrorId 'InvalidGuid' ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Value + return + } + + break + } + + $script:Description { $KeyName = $script:Description } + + $script:CompanyName { $KeyName = $script:CompanyName } + + $script:Copyright { $KeyName = $script:Copyright } + + $script:Tags { + $KeyName = $script:Tags + $Value = $Value -split '[,\s+]' | Microsoft.PowerShell.Core\Where-Object {$_} + break + } + + $script:LicenseUri { + $KeyName = $script:LicenseUri + if(-not (Test-WebUri -Uri $Value)) + { + $message = $LocalizedData.InvalidWebUri -f ($LicenseUri, "LicenseUri") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Value + return + } + + $Value = [Uri]$Value + } + + $script:ProjectUri { + $KeyName = $script:ProjectUri + if(-not (Test-WebUri -Uri $Value)) + { + $message = $LocalizedData.InvalidWebUri -f ($ProjectUri, "ProjectUri") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Value + return + } + + $Value = [Uri]$Value + } + + $script:IconUri { + $KeyName = $script:IconUri + if(-not (Test-WebUri -Uri $Value)) + { + $message = $LocalizedData.InvalidWebUri -f ($IconUri, "IconUri") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Value + return + } + + $Value = [Uri]$Value + } + + $script:ExternalModuleDependencies { + $KeyName = $script:ExternalModuleDependencies + $Value = $Value -split '[,\s+]' | Microsoft.PowerShell.Core\Where-Object {$_} + } + + $script:ReleaseNotes { $KeyName = $script:ReleaseNotes } + + $script:RequiredModules { $KeyName = $script:RequiredModules } + + $script:RequiredScripts { + $KeyName = $script:RequiredScripts + $Value = $Value -split '[,\s+]' | Microsoft.PowerShell.Core\Where-Object {$_} + } + + $script:ExternalScriptDependencies { + $KeyName = $script:ExternalScriptDependencies + $Value = $Value -split '[,\s+]' | Microsoft.PowerShell.Core\Where-Object {$_} + } + + $script:DefinedCommands { $KeyName = $script:DefinedCommands } + + $script:DefinedFunctions { $KeyName = $script:DefinedFunctions } + + $script:DefinedWorkflows { $KeyName = $script:DefinedWorkflows } + } + + Microsoft.PowerShell.Utility\Add-Member -InputObject $PSScriptInfo ` + -MemberType NoteProperty ` + -Name $KeyName ` + -Value $Value ` + -Force +} + +function Get-ExportedDscResources +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [PSModuleInfo] + $PSModuleInfo + ) + + $dscResources = @() + + if(Get-Command -Name Get-DscResource -Module PSDesiredStateConfiguration -ErrorAction SilentlyContinue) + { + $OldPSModulePath = $env:PSModulePath + + try + { + $env:PSModulePath = Join-Path -Path $PSHOME -ChildPath "Modules" + $env:PSModulePath = "$env:PSModulePath;$(Split-Path -Path $PSModuleInfo.ModuleBase -Parent)" + + $dscResources = PSDesiredStateConfiguration\Get-DscResource -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | + Microsoft.PowerShell.Core\ForEach-Object { + if($_.Module -and ($_.Module.Name -eq $PSModuleInfo.Name)) + { + $_.Name + } + } + } + finally + { + $env:PSModulePath = $OldPSModulePath + } + } + else + { + $dscResourcesDir = Microsoft.PowerShell.Management\Join-Path -Path $PSModuleInfo.ModuleBase -ChildPath "DscResources" + if(Microsoft.PowerShell.Management\Test-Path $dscResourcesDir) + { + $dscResources = Microsoft.PowerShell.Management\Get-ChildItem -Path $dscResourcesDir -Directory -Name + } + } + + return $dscResources +} + +function Get-AvailableRoleCapabilityName +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [PSModuleInfo] + $PSModuleInfo + ) + + $RoleCapabilityNames = @() + + $RoleCapabilitiesDir = Microsoft.PowerShell.Management\Join-Path -Path $PSModuleInfo.ModuleBase -ChildPath 'RoleCapabilities' + if(Microsoft.PowerShell.Management\Test-Path -Path $RoleCapabilitiesDir -PathType Container) + { + $RoleCapabilityNames = Microsoft.PowerShell.Management\Get-ChildItem -Path $RoleCapabilitiesDir ` + -Name -Filter *.psrc | + ForEach-Object {[System.IO.Path]::GetFileNameWithoutExtension($_)} + } + + return $RoleCapabilityNames +} + +function Get-LocationString +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter()] + [Uri] + $LocationUri + ) + + $LocationString = $null + + if($LocationUri) + { + if($LocationUri.Scheme -eq 'file') + { + $LocationString = $LocationUri.OriginalString + } + elseif($LocationUri.AbsoluteUri) + { + $LocationString = $LocationUri.AbsoluteUri + } + else + { + $LocationString = $LocationUri.ToString() + } + } + + return $LocationString +} + +#endregion Utility functions + +#region PowerShellGet Provider APIs Implementation +function Get-PackageProviderName +{ + return $script:PSModuleProviderName +} + +function Get-Feature +{ + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Get-Feature')) + Write-Output -InputObject (New-Feature $script:SupportsPSModulesFeatureName ) +} + +function Initialize-Provider +{ + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Initialize-Provider')) +} + +function Get-DynamicOptions +{ + param + ( + [Microsoft.PackageManagement.MetaProvider.PowerShell.OptionCategory] + $category + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Get-DynamicOptions')) + + Write-Output -InputObject (New-DynamicOption -Category $category -Name $script:PackageManagementProviderParam -ExpectedType String -IsRequired $false) + + Write-Output -InputObject (New-DynamicOption -Category $category ` + -Name $script:PSArtifactType ` + -ExpectedType String ` + -IsRequired $false ` + -PermittedValues @($script:PSArtifactTypeModule,$script:PSArtifactTypeScript, $script:All)) + + switch($category) + { + Package { + Write-Output -InputObject (New-DynamicOption -Category $category -Name $script:Filter -ExpectedType String -IsRequired $false) + Write-Output -InputObject (New-DynamicOption -Category $category -Name $script:Tag -ExpectedType StringArray -IsRequired $false) + Write-Output -InputObject (New-DynamicOption -Category $category -Name Includes -ExpectedType StringArray -IsRequired $false -PermittedValues $script:IncludeValidSet) + Write-Output -InputObject (New-DynamicOption -Category $category -Name DscResource -ExpectedType StringArray -IsRequired $false) + Write-Output -InputObject (New-DynamicOption -Category $category -Name RoleCapability -ExpectedType StringArray -IsRequired $false) + Write-Output -InputObject (New-DynamicOption -Category $category -Name Command -ExpectedType StringArray -IsRequired $false) + } + + Source { + Write-Output -InputObject (New-DynamicOption -Category $category -Name $script:PublishLocation -ExpectedType String -IsRequired $false) + Write-Output -InputObject (New-DynamicOption -Category $category -Name $script:ScriptSourceLocation -ExpectedType String -IsRequired $false) + Write-Output -InputObject (New-DynamicOption -Category $category -Name $script:ScriptPublishLocation -ExpectedType String -IsRequired $false) + } + + Install + { + Write-Output -InputObject (New-DynamicOption -Category $category -Name "Scope" -ExpectedType String -IsRequired $false -PermittedValues @("CurrentUser","AllUsers")) + Write-Output -InputObject (New-DynamicOption -Category $category -Name "InstallUpdate" -ExpectedType Switch -IsRequired $false) + Write-Output -InputObject (New-DynamicOption -Category $category -Name 'NoPathUpdate' -ExpectedType Switch -IsRequired $false) + } + } +} + +function Add-PackageSource +{ + [CmdletBinding()] + param + ( + [string] + $Name, + + [string] + $Location, + + [bool] + $Trusted + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Add-PackageSource')) + + Set-ModuleSourcesVariable -Force + + $IsNewModuleSource = $false + $Options = $request.Options + + foreach( $o in $Options.Keys ) + { + Write-Debug ( "OPTION: {0} => {1}" -f ($o, $Options[$o]) ) + } + + if($Options.ContainsKey('IsNewModuleSource')) + { + $IsNewModuleSource = $Options['IsNewModuleSource'] + + if($IsNewModuleSource.GetType().ToString() -eq 'System.String') + { + if($IsNewModuleSource -eq 'false') + { + $IsNewModuleSource = $false + } + elseif($IsNewModuleSource -eq 'true') + { + $IsNewModuleSource = $true + } + } + } + + $IsUpdatePackageSource = $false + if($Options.ContainsKey('IsUpdatePackageSource')) + { + $IsUpdatePackageSource = $Options['IsUpdatePackageSource'] + + if($IsUpdatePackageSource.GetType().ToString() -eq 'System.String') + { + if($IsUpdatePackageSource -eq 'false') + { + $IsUpdatePackageSource = $false + } + elseif($IsUpdatePackageSource -eq 'true') + { + $IsUpdatePackageSource = $true + } + } + } + + $PublishLocation = $null + if($Options.ContainsKey($script:PublishLocation)) + { + $PublishLocation = $Options[$script:PublishLocation] + + if(-not (Microsoft.PowerShell.Management\Test-Path $PublishLocation) -and + -not (Test-WebUri -uri $PublishLocation)) + { + $PublishLocationUri = [Uri]$PublishLocation + if($PublishLocationUri.Scheme -eq 'file') + { + $message = $LocalizedData.PathNotFound -f ($PublishLocation) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $PublishLocation + } + else + { + $message = $LocalizedData.InvalidWebUri -f ($PublishLocation, "PublishLocation") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $PublishLocation + } + } + } + + $ScriptSourceLocation = $null + if($Options.ContainsKey($script:ScriptSourceLocation)) + { + $ScriptSourceLocation = $Options[$script:ScriptSourceLocation] + + if(-not (Microsoft.PowerShell.Management\Test-Path $ScriptSourceLocation) -and + -not (Test-WebUri -uri $ScriptSourceLocation)) + { + $ScriptSourceLocationUri = [Uri]$ScriptSourceLocation + if($ScriptSourceLocationUri.Scheme -eq 'file') + { + $message = $LocalizedData.PathNotFound -f ($ScriptSourceLocation) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $ScriptSourceLocation + } + else + { + $message = $LocalizedData.InvalidWebUri -f ($ScriptSourceLocation, "ScriptSourceLocation") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $ScriptSourceLocation + } + } + } + + $ScriptPublishLocation = $null + if($Options.ContainsKey($script:ScriptPublishLocation)) + { + $ScriptPublishLocation = $Options[$script:ScriptPublishLocation] + + if(-not (Microsoft.PowerShell.Management\Test-Path $ScriptPublishLocation) -and + -not (Test-WebUri -uri $ScriptPublishLocation)) + { + $ScriptPublishLocationUri = [Uri]$ScriptPublishLocation + if($ScriptPublishLocationUri.Scheme -eq 'file') + { + $message = $LocalizedData.PathNotFound -f ($ScriptPublishLocation) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $ScriptPublishLocation + } + else + { + $message = $LocalizedData.InvalidWebUri -f ($ScriptPublishLocation, "ScriptPublishLocation") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $ScriptPublishLocation + } + } + } + + # Ping and resolve the specified location + $Location = Resolve-Location -Location $Location ` + -LocationParameterName 'Location' ` + -CallerPSCmdlet $PSCmdlet + if(-not $Location) + { + # Above Resolve-Location function throws an error when it is not able to resolve a location + return + } + + if(-not (Microsoft.PowerShell.Management\Test-Path -Path $Location) -and + -not (Test-WebUri -uri $Location) ) + { + $LocationUri = [Uri]$Location + if($LocationUri.Scheme -eq 'file') + { + $message = $LocalizedData.PathNotFound -f ($Location) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Location + } + else + { + $message = $LocalizedData.InvalidWebUri -f ($Location, "Location") + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Location + } + } + + if(Test-WildcardPattern $Name) + { + $message = $LocalizedData.RepositoryNameContainsWildCards -f ($Name) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "RepositoryNameContainsWildCards" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Name + } + + $LocationString = Get-ValidModuleLocation -LocationString $Location -ParameterName "Location" + + # Check if Location is already registered with another Name + $existingSourceName = Get-SourceName -Location $LocationString + + if($existingSourceName -and + ($Name -ne $existingSourceName) -and + -not $IsNewModuleSource) + { + $message = $LocalizedData.RepositoryAlreadyRegistered -f ($existingSourceName, $Location, $Name) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "RepositoryAlreadyRegistered" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + + $currentSourceObject = $null + + # Check if Name is already registered + if($script:PSGetModuleSources.Contains($Name)) + { + $currentSourceObject = $script:PSGetModuleSources[$Name] + } + + if(-not $PublishLocation -and $currentSourceObject -and $currentSourceObject.PublishLocation) + { + $PublishLocation = $currentSourceObject.PublishLocation + } + + if((-not $ScriptPublishLocation) -and + $currentSourceObject -and + (Get-Member -InputObject $currentSourceObject -Name $script:ScriptPublishLocation) -and + $currentSourceObject.ScriptPublishLocation) + { + $ScriptPublishLocation = $currentSourceObject.ScriptPublishLocation + } + + if((-not $ScriptSourceLocation) -and + $currentSourceObject -and + (Get-Member -InputObject $currentSourceObject -Name $script:ScriptSourceLocation) -and + $currentSourceObject.ScriptSourceLocation) + { + $ScriptSourceLocation = $currentSourceObject.ScriptSourceLocation + } + + $IsProviderSpecified = $false; + if ($Options.ContainsKey($script:PackageManagementProviderParam)) + { + $SpecifiedProviderName = $Options[$script:PackageManagementProviderParam] + + $IsProviderSpecified = $true + + Write-Verbose ($LocalizedData.SpecifiedProviderName -f $SpecifiedProviderName) + if ($SpecifiedProviderName -eq $script:PSModuleProviderName) + { + $message = $LocalizedData.InvalidPackageManagementProviderValue -f ($SpecifiedProviderName, $script:NuGetProviderName, $script:NuGetProviderName) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidPackageManagementProviderValue" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $SpecifiedProviderName + return + } + } + else + { + $SpecifiedProviderName = $script:NuGetProviderName + Write-Verbose ($LocalizedData.ProviderNameNotSpecified -f $SpecifiedProviderName) + } + + $packageSource = $null + + $selProviders = $request.SelectProvider($SpecifiedProviderName) + + if(-not $selProviders -and $IsProviderSpecified) + { + $message = $LocalizedData.SpecifiedProviderNotAvailable -f $SpecifiedProviderName + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "SpecifiedProviderNotAvailable" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $SpecifiedProviderName + } + + # Try with user specified provider or NuGet provider + foreach($SelectedProvider in $selProviders) + { + if($request.IsCanceled) + { + return + } + + if($SelectedProvider -and $SelectedProvider.Features.ContainsKey($script:SupportsPSModulesFeatureName)) + { + $packageSource = $SelectedProvider.ResolvePackageSources( (New-Request -Sources @($LocationString)) ) + } + else + { + $message = $LocalizedData.SpecifiedProviderDoesnotSupportPSModules -f $SelectedProvider.ProviderName + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "SpecifiedProviderDoesnotSupportPSModules" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $SelectedProvider.ProviderName + } + + if($packageSource) + { + break + } + } + + # Poll other package provider when NuGet provider doesn't resolves the specified location + if(-not $packageSource -and -not $IsProviderSpecified) + { + Write-Verbose ($LocalizedData.PollingPackageManagementProvidersForLocation -f $LocationString) + + $moduleProviders = $request.SelectProvidersWithFeature($script:SupportsPSModulesFeatureName) + + foreach($provider in $moduleProviders) + { + if($request.IsCanceled) + { + return + } + + # Skip already tried $SpecifiedProviderName and PowerShellGet provider + if($provider.ProviderName -eq $SpecifiedProviderName -or + $provider.ProviderName -eq $script:PSModuleProviderName) + { + continue + } + + Write-Verbose ($LocalizedData.PollingSingleProviderForLocation -f ($LocationString, $provider.ProviderName)) + $packageSource = $provider.ResolvePackageSources((New-Request -Option @{} -Sources @($LocationString))) + + if($packageSource) + { + Write-Verbose ($LocalizedData.FoundProviderForLocation -f ($provider.ProviderName, $Location)) + $SelectedProvider = $provider + break + } + } + } + + if(-not $packageSource) + { + $message = $LocalizedData.SpecifiedLocationCannotBeRegistered -f $Location + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "SpecifiedLocationCannotBeRegistered" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $Location + } + + $ProviderOptions = @{} + + $SelectedProvider.DynamicOptions | Microsoft.PowerShell.Core\ForEach-Object { + if($options.ContainsKey($_.Name) ) + { + $ProviderOptions[$_.Name] = $options[$_.Name] + } + } + + # Keep the existing provider options if not specified in Set-PSRepository + if($currentSourceObject) + { + $currentSourceObject.ProviderOptions.GetEnumerator() | Microsoft.PowerShell.Core\ForEach-Object { + if (-not $ProviderOptions.ContainsKey($_.Key) ) + { + $ProviderOptions[$_.Key] = $_.Value + } + } + } + + if(-not $PublishLocation) + { + $PublishLocation = Get-PublishLocation -Location $LocationString + } + + # Use the PublishLocation for the scripts when ScriptPublishLocation is not specified by the user + if(-not $ScriptPublishLocation) + { + $ScriptPublishLocation = $PublishLocation + + # ScriptPublishLocation and PublishLocation should be equal in case of SMB Share or Local directory paths + if($Options.ContainsKey($script:ScriptPublishLocation) -and + (Microsoft.PowerShell.Management\Test-Path -Path $ScriptPublishLocation)) + { + if($ScriptPublishLocation -ne $PublishLocation) + { + $message = $LocalizedData.PublishLocationPathsForModulesAndScriptsShouldBeEqual -f ($LocationString, $ScriptSourceLocation) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "PublishLocationPathsForModulesAndScriptsShouldBeEqual" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $Location + } + } + } + + if(-not $ScriptSourceLocation) + { + $ScriptSourceLocation = Get-ScriptSourceLocation -Location $LocationString + } + elseif($Options.ContainsKey($script:ScriptSourceLocation)) + { + # ScriptSourceLocation and SourceLocation cannot be same for they are URLs + # Both should be equal in case of SMB Share or Local directory paths + if(Microsoft.PowerShell.Management\Test-Path -Path $ScriptSourceLocation) + { + if($ScriptSourceLocation -ne $LocationString) + { + $message = $LocalizedData.SourceLocationPathsForModulesAndScriptsShouldBeEqual -f ($LocationString, $ScriptSourceLocation) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "SourceLocationPathsForModulesAndScriptsShouldBeEqual" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $Location + } + } + else + { + if($ScriptSourceLocation -eq $LocationString) + { + $message = $LocalizedData.SourceLocationUrisForModulesAndScriptsShouldBeDifferent -f ($LocationString, $ScriptSourceLocation) + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "SourceLocationUrisForModulesAndScriptsShouldBeDifferent" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation ` + -ExceptionObject $Location + } + } + } + + # no error so we can safely remove the source + if($script:PSGetModuleSources.Contains($Name)) + { + $null = $script:PSGetModuleSources.Remove($Name) + } + + # Add new module source + $moduleSource = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + Name = $Name + SourceLocation = $LocationString + PublishLocation = $PublishLocation + ScriptSourceLocation = $ScriptSourceLocation + ScriptPublishLocation = $ScriptPublishLocation + Trusted=$Trusted + Registered= (-not $IsNewModuleSource) + InstallationPolicy = if($Trusted) {'Trusted'} else {'Untrusted'} + PackageManagementProvider = $SelectedProvider.ProviderName + ProviderOptions = $ProviderOptions + }) + + #region telemetry - Capture non-PSGallery registrations as telemetry events + if ($script:TelemetryEnabled) + { + + Log-NonPSGalleryRegistration -sourceLocation $moduleSource.SourceLocation ` + -installationPolicy $moduleSource.InstallationPolicy ` + -packageManagementProvider $moduleSource.PackageManagementProvider ` + -publishLocation $moduleSource.PublishLocation ` + -scriptSourceLocation $moduleSource.ScriptSourceLocation ` + -scriptPublishLocation $moduleSource.ScriptPublishLocation ` + -operationName PSGET_NONPSGALLERY_REGISTRATION ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + + } + #endregion + + $moduleSource.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.PSRepository") + + # Persist the repositories only when Register-PSRepository cmdlet is used + if(-not $IsNewModuleSource) + { + $script:PSGetModuleSources.Add($Name, $moduleSource) + + $message = $LocalizedData.RepositoryRegistered -f ($Name, $LocationString) + Write-Verbose $message + + # Persist the module sources + Save-ModuleSources + } + + # return the package source object. + Write-Output -InputObject (New-PackageSourceFromModuleSource -ModuleSource $moduleSource) +} + +function Resolve-PackageSource +{ + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Resolve-PackageSource')) + + Set-ModuleSourcesVariable + + $SourceName = $request.PackageSources + + if(-not $SourceName) + { + $SourceName = "*" + } + + foreach($moduleSourceName in $SourceName) + { + if($request.IsCanceled) + { + return + } + + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $moduleSourceName,$script:wildcardOptions + $moduleSourceFound = $false + + $script:PSGetModuleSources.GetEnumerator() | + Microsoft.PowerShell.Core\Where-Object {$wildcardPattern.IsMatch($_.Key)} | + Microsoft.PowerShell.Core\ForEach-Object { + + $moduleSource = $script:PSGetModuleSources[$_.Key] + + $packageSource = New-PackageSourceFromModuleSource -ModuleSource $moduleSource + + Write-Output -InputObject $packageSource + + $moduleSourceFound = $true + } + + if(-not $moduleSourceFound) + { + $sourceName = Get-SourceName -Location $moduleSourceName + + if($sourceName) + { + $moduleSource = $script:PSGetModuleSources[$sourceName] + + $packageSource = New-PackageSourceFromModuleSource -ModuleSource $moduleSource + + Write-Output -InputObject $packageSource + } + elseif( -not (Test-WildcardPattern $moduleSourceName)) + { + $message = $LocalizedData.RepositoryNotFound -f ($moduleSourceName) + + Write-Error -Message $message -ErrorId "RepositoryNotFound" -Category InvalidOperation -TargetObject $moduleSourceName + } + } + } +} + +function Remove-PackageSource +{ + param + ( + [string] + $Name + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Remove-PackageSource')) + + Set-ModuleSourcesVariable -Force + + $ModuleSourcesToBeRemoved = @() + + foreach ($moduleSourceName in $Name) + { + if($request.IsCanceled) + { + return + } + + # Check if $Name contains any wildcards + if(Test-WildcardPattern $moduleSourceName) + { + $message = $LocalizedData.RepositoryNameContainsWildCards -f ($moduleSourceName) + Write-Error -Message $message -ErrorId "RepositoryNameContainsWildCards" -Category InvalidOperation -TargetObject $moduleSourceName + continue + } + + # Check if the specified module source name is in the registered module sources + if(-not $script:PSGetModuleSources.Contains($moduleSourceName)) + { + $message = $LocalizedData.RepositoryNotFound -f ($moduleSourceName) + Write-Error -Message $message -ErrorId "RepositoryNotFound" -Category InvalidOperation -TargetObject $moduleSourceName + continue + } + + $ModuleSourcesToBeRemoved += $moduleSourceName + $message = $LocalizedData.RepositoryUnregistered -f ($moduleSourceName) + Write-Verbose $message + } + + # Remove the module source + $ModuleSourcesToBeRemoved | Microsoft.PowerShell.Core\ForEach-Object { $null = $script:PSGetModuleSources.Remove($_) } + + # Persist the module sources + Save-ModuleSources +} + +function Find-Package +{ + [CmdletBinding()] + param + ( + [string[]] + $names, + + [string] + $requiredVersion, + + [string] + $minimumVersion, + + [string] + $maximumVersion + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Find-Package')) + + Set-ModuleSourcesVariable + + if($RequiredVersion -and $MinimumVersion) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.VersionRangeAndRequiredVersionCannotBeSpecifiedTogether ` + -ErrorId "VersionRangeAndRequiredVersionCannotBeSpecifiedTogether" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + + if($RequiredVersion -or $MinimumVersion) + { + if(-not $names -or $names.Count -ne 1 -or (Test-WildcardPattern -Name $names[0])) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.VersionParametersAreAllowedOnlyWithSingleName ` + -ErrorId "VersionParametersAreAllowedOnlyWithSingleName" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + } + + $options = $request.Options + + foreach( $o in $options.Keys ) + { + Write-Debug ( "OPTION: {0} => {1}" -f ($o, $options[$o]) ) + } + + # When using -Name, we don't send PSGet-specific properties to the server - we will filter it ourselves + $postFilter = New-Object -TypeName System.Collections.Hashtable + if($options.ContainsKey("Name")) + { + if($options.ContainsKey("Includes")) + { + $postFilter["Includes"] = $options["Includes"] + $null = $options.Remove("Includes") + } + + if($options.ContainsKey("DscResource")) + { + $postFilter["DscResource"] = $options["DscResource"] + $null = $options.Remove("DscResource") + } + + if($options.ContainsKey('RoleCapability')) + { + $postFilter['RoleCapability'] = $options['RoleCapability'] + $null = $options.Remove('RoleCapability') + } + + if($options.ContainsKey("Command")) + { + $postFilter["Command"] = $options["Command"] + $null = $options.Remove("Command") + } + } + + $LocationOGPHashtable = [ordered]@{} + if($options -and $options.ContainsKey('Source')) + { + $SourceNames = $($options['Source']) + + Write-Verbose ($LocalizedData.SpecifiedSourceName -f ($SourceNames)) + + foreach($sourceName in $SourceNames) + { + if($script:PSGetModuleSources.Contains($sourceName)) + { + $ModuleSource = $script:PSGetModuleSources[$sourceName] + $LocationOGPHashtable[$ModuleSource.SourceLocation] = (Get-ProviderName -PSCustomObject $ModuleSource) + } + else + { + $sourceByLocation = Get-SourceName -Location $sourceName + + if ($sourceByLocation) + { + $ModuleSource = $script:PSGetModuleSources[$sourceByLocation] + $LocationOGPHashtable[$ModuleSource.SourceLocation] = (Get-ProviderName -PSCustomObject $ModuleSource) + } + else + { + $message = $LocalizedData.RepositoryNotFound -f ($sourceName) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "RepositoryNotFound" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $sourceName + } + } + } + } + elseif($options -and + $options.ContainsKey($script:PackageManagementProviderParam) -and + $options.ContainsKey('Location')) + { + $Location = $options['Location'] + $PackageManagementProvider = $options['PackageManagementProvider'] + + Write-Verbose ($LocalizedData.SpecifiedLocationAndOGP -f ($Location, $PackageManagementProvider)) + + $LocationOGPHashtable[$Location] = $PackageManagementProvider + } + else + { + Write-Verbose $LocalizedData.NoSourceNameIsSpecified + + $script:PSGetModuleSources.Values | Microsoft.PowerShell.Core\ForEach-Object { $LocationOGPHashtable[$_.SourceLocation] = (Get-ProviderName -PSCustomObject $_) } + } + + $artifactTypes = $script:PSArtifactTypeModule + if($options.ContainsKey($script:PSArtifactType)) + { + $artifactTypes = $options[$script:PSArtifactType] + } + + if($artifactTypes -eq $script:All) + { + $artifactTypes = @($script:PSArtifactTypeModule,$script:PSArtifactTypeScript) + } + + $providerOptions = @{} + + if($options.ContainsKey($script:AllVersions)) + { + $providerOptions[$script:AllVersions] = $options[$script:AllVersions] + } + + if($options.ContainsKey($script:Filter)) + { + $Filter = $options[$script:Filter] + $providerOptions['Contains'] = $Filter + } + + if($options.ContainsKey($script:Tag)) + { + $userSpecifiedTags = $options[$script:Tag] | Microsoft.PowerShell.Utility\Select-Object -Unique + } + else + { + $userSpecifiedTags = @($script:NotSpecified) + } + + $specifiedDscResources = @() + if($options.ContainsKey('DscResource')) + { + $specifiedDscResources = $options['DscResource'] | + Microsoft.PowerShell.Utility\Select-Object -Unique | + Microsoft.PowerShell.Core\ForEach-Object {"$($script:DscResource)_$_"} + } + + $specifiedRoleCapabilities = @() + if($options.ContainsKey('RoleCapability')) + { + $specifiedRoleCapabilities = $options['RoleCapability'] | + Microsoft.PowerShell.Utility\Select-Object -Unique | + Microsoft.PowerShell.Core\ForEach-Object {"$($script:RoleCapability)_$_"} + } + + $specifiedCommands = @() + if($options.ContainsKey('Command')) + { + $specifiedCommands = $options['Command'] | + Microsoft.PowerShell.Utility\Select-Object -Unique | + Microsoft.PowerShell.Core\ForEach-Object {"$($script:Command)_$_"} + } + + $specifiedIncludes = @() + if($options.ContainsKey('Includes')) + { + $includes = $options['Includes'] | + Microsoft.PowerShell.Utility\Select-Object -Unique | + Microsoft.PowerShell.Core\ForEach-Object {"$($script:Includes)_$_"} + + # Add PSIncludes_DscResource to $specifiedIncludes iff -DscResource names are not specified + # Add PSIncludes_RoleCapability to $specifiedIncludes iff -RoleCapability names are not specified + # Add PSIncludes_Cmdlet or PSIncludes_Function to $specifiedIncludes iff -Command names are not specified + # otherwise $script:NotSpecified will be added to $specifiedIncludes + if($includes) + { + if(-not $specifiedDscResources -and ($includes -contains "$($script:Includes)_DscResource") ) + { + $specifiedIncludes += "$($script:Includes)_DscResource" + } + + if(-not $specifiedRoleCapabilities -and ($includes -contains "$($script:Includes)_RoleCapability") ) + { + $specifiedIncludes += "$($script:Includes)_RoleCapability" + } + + if(-not $specifiedCommands) + { + if($includes -contains "$($script:Includes)_Cmdlet") + { + $specifiedIncludes += "$($script:Includes)_Cmdlet" + } + + if($includes -contains "$($script:Includes)_Function") + { + $specifiedIncludes += "$($script:Includes)_Function" + } + + if($includes -contains "$($script:Includes)_Workflow") + { + $specifiedIncludes += "$($script:Includes)_Workflow" + } + } + } + } + + if(-not $specifiedDscResources) + { + $specifiedDscResources += $script:NotSpecified + } + + if(-not $specifiedRoleCapabilities) + { + $specifiedRoleCapabilities += $script:NotSpecified + } + + if(-not $specifiedCommands) + { + $specifiedCommands += $script:NotSpecified + } + + if(-not $specifiedIncludes) + { + $specifiedIncludes += $script:NotSpecified + } + + $providerSearchTags = @{} + + foreach($tag in $userSpecifiedTags) + { + foreach($include in $specifiedIncludes) + { + foreach($command in $specifiedCommands) + { + foreach($resource in $specifiedDscResources) + { + foreach($roleCapability in $specifiedRoleCapabilities) + { + $providerTags = @() + if($resource -ne $script:NotSpecified) + { + $providerTags += $resource + } + + if($roleCapability -ne $script:NotSpecified) + { + $providerTags += $roleCapability + } + + if($command -ne $script:NotSpecified) + { + $providerTags += $command + } + + if($include -ne $script:NotSpecified) + { + $providerTags += $include + } + + if($tag -ne $script:NotSpecified) + { + $providerTags += $tag + } + + if($providerTags) + { + $providerSearchTags["$tag $resource $roleCapability $command $include"] = $providerTags + } + } + } + } + } + } + + $InstallationPolicy = "Untrusted" + if($options.ContainsKey('InstallationPolicy')) + { + $InstallationPolicy = $options['InstallationPolicy'] + } + + $streamedResults = @() + + foreach($artifactType in $artifactTypes) + { + foreach($kvPair in $LocationOGPHashtable.GetEnumerator()) + { + if($request.IsCanceled) + { + return + } + + $Location = $kvPair.Key + if($artifactType -eq $script:PSArtifactTypeScript) + { + $sourceName = Get-SourceName -Location $Location + + if($SourceName) + { + $ModuleSource = $script:PSGetModuleSources[$SourceName] + + # Skip source if no ScriptSourceLocation is available. + if(-not $ModuleSource.ScriptSourceLocation) + { + if($options.ContainsKey('Source')) + { + $message = $LocalizedData.ScriptSourceLocationIsMissing -f ($ModuleSource.Name) + Write-Error -Message $message ` + -ErrorId 'ScriptSourceLocationIsMissing' ` + -Category InvalidArgument ` + -TargetObject $ModuleSource.Name + } + + continue + } + + $Location = $ModuleSource.ScriptSourceLocation + } + } + + $ProviderName = $kvPair.Value + + Write-Verbose ($LocalizedData.GettingPackageManagementProviderObject -f ($ProviderName)) + + $provider = $request.SelectProvider($ProviderName) + + if(-not $provider) + { + Write-Error -Message ($LocalizedData.PackageManagementProviderIsNotAvailable -f $ProviderName) + + Continue + } + + Write-Verbose ($LocalizedData.SpecifiedLocationAndOGP -f ($Location, $provider.ProviderName)) + + if($providerSearchTags.Values.Count) + { + $tagList = $providerSearchTags.Values + } + else + { + $tagList = @($script:NotSpecified) + } + + $namesParameterEmpty = ($names.Count -eq 1) -and ($names[0] -eq '') + + foreach($providerTag in $tagList) + { + if($request.IsCanceled) + { + return + } + + $FilterOnTag = @() + + if($providerTag -ne $script:NotSpecified) + { + $FilterOnTag = $providerTag + } + + if(Microsoft.PowerShell.Management\Test-Path -Path $Location) + { + if($artifactType -eq $script:PSArtifactTypeScript) + { + $FilterOnTag += 'PSScript' + } + elseif($artifactType -eq $script:PSArtifactTypeModule) + { + $FilterOnTag += 'PSModule' + } + } + + if($FilterOnTag) + { + $providerOptions["FilterOnTag"] = $FilterOnTag + } + elseif($providerOptions.ContainsKey('FilterOnTag')) + { + $null = $providerOptions.Remove('FilterOnTag') + } + + if($request.Options.ContainsKey($script:FindByCanonicalId)) + { + $providerOptions[$script:FindByCanonicalId] = $request.Options[$script:FindByCanonicalId] + } + + $providerOptions["Headers"] = 'PSGalleryClientVersion=1.1' + + $pkgs = $provider.FindPackages($names, + $requiredVersion, + $minimumVersion, + $maximumVersion, + (New-Request -Sources @($Location) -Options $providerOptions) ) + + foreach($pkg in $pkgs) + { + if($request.IsCanceled) + { + return + } + + # $pkg.Name has to match any of the supplied names, using PowerShell wildcards + if ($namesParameterEmpty -or ($names | % { if ($pkg.Name -like $_){return $true; break} } -End {return $false})) + { + $includePackage = $true + + # If -Name was provided, we need to post-filter + # Filtering has AND semantics between different parameters and OR within a parameter (each parameter is potentially an array) + if($options.ContainsKey("Name") -and $postFilter.Count -gt 0) + { + if ($pkg.Metadata["DscResources"].Count -gt 0) + { + $pkgDscResources = $pkg.Metadata["DscResources"] -Split " " | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + } + else + { + $pkgDscResources = $pkg.Metadata["tags"] -Split " " ` + | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } ` + | Microsoft.PowerShell.Core\Where-Object { $_.StartsWith($script:DscResource, [System.StringComparison]::OrdinalIgnoreCase) } ` + | Microsoft.PowerShell.Core\ForEach-Object { $_.Substring($script:DscResource.Length + 1) } + } + + if ($pkg.Metadata['RoleCapabilities'].Count -gt 0) + { + $pkgRoleCapabilities = $pkg.Metadata['RoleCapabilities'] -Split ' ' | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + } + else + { + $pkgRoleCapabilities = $pkg.Metadata["tags"] -Split ' ' ` + | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } ` + | Microsoft.PowerShell.Core\Where-Object { $_.StartsWith($script:RoleCapability, [System.StringComparison]::OrdinalIgnoreCase) } ` + | Microsoft.PowerShell.Core\ForEach-Object { $_.Substring($script:RoleCapability.Length + 1) } + } + + if ($pkg.Metadata["Functions"].Count -gt 0) + { + $pkgFunctions = $pkg.Metadata["Functions"] -Split " " | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + } + else + { + $pkgFunctions = $pkg.Metadata["tags"] -Split " " ` + | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } ` + | Microsoft.PowerShell.Core\Where-Object { $_.StartsWith($script:Function, [System.StringComparison]::OrdinalIgnoreCase) } ` + | Microsoft.PowerShell.Core\ForEach-Object { $_.Substring($script:Function.Length + 1) } + } + + if ($pkg.Metadata["Cmdlets"].Count -gt 0) + { + $pkgCmdlets = $pkg.Metadata["Cmdlets"] -Split " " | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + } + else + { + $pkgCmdlets = $pkg.Metadata["tags"] -Split " " ` + | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } ` + | Microsoft.PowerShell.Core\Where-Object { $_.StartsWith($script:Cmdlet, [System.StringComparison]::OrdinalIgnoreCase) } ` + | Microsoft.PowerShell.Core\ForEach-Object { $_.Substring($script:Cmdlet.Length + 1) } + } + + if ($pkg.Metadata["Workflows"].Count -gt 0) + { + $pkgWorkflows = $pkg.Metadata["Workflows"] -Split " " | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } + } + else + { + $pkgWorkflows = $pkg.Metadata["tags"] -Split " " ` + | Microsoft.PowerShell.Core\Where-Object { $_.Trim() } ` + | Microsoft.PowerShell.Core\Where-Object { $_.StartsWith($script:Workflow, [System.StringComparison]::OrdinalIgnoreCase) } ` + | Microsoft.PowerShell.Core\ForEach-Object { $_.Substring($script:Workflow.Length + 1) } + } + + foreach ($key in $postFilter.Keys) + { + switch ($key) + { + "DscResource" { + $values = $postFilter[$key] + + $includePackage = $false + + foreach ($value in $values) + { + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $value,$script:wildcardOptions + + $pkgDscResources | Microsoft.PowerShell.Core\ForEach-Object { + if ($wildcardPattern.IsMatch($_)) + { + $includePackage = $true + break + } + } + } + + if (-not $includePackage) + { + break + } + } + + 'RoleCapability' { + $values = $postFilter[$key] + + $includePackage = $false + + foreach ($value in $values) + { + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $value,$script:wildcardOptions + + $pkgRoleCapabilities | Microsoft.PowerShell.Core\ForEach-Object { + if ($wildcardPattern.IsMatch($_)) + { + $includePackage = $true + break + } + } + } + + if (-not $includePackage) + { + break + } + } + + "Command" { + $values = $postFilter[$key] + + $includePackage = $false + + foreach ($value in $values) + { + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $value,$script:wildcardOptions + + $pkgFunctions | Microsoft.PowerShell.Core\ForEach-Object { + if ($wildcardPattern.IsMatch($_)) + { + $includePackage = $true + break + } + } + + $pkgCmdlets | Microsoft.PowerShell.Core\ForEach-Object { + if ($wildcardPattern.IsMatch($_)) + { + $includePackage = $true + break + } + } + + $pkgWorkflows | Microsoft.PowerShell.Core\ForEach-Object { + if ($wildcardPattern.IsMatch($_)) + { + $includePackage = $true + break + } + } + } + + if (-not $includePackage) + { + break + } + } + + "Includes" { + $values = $postFilter[$key] + + $includePackage = $false + + foreach ($value in $values) + { + switch ($value) + { + "Cmdlet" { if ($pkgCmdlets ) { $includePackage = $true } } + "Function" { if ($pkgFunctions ) { $includePackage = $true } } + "DscResource" { if ($pkgDscResources ) { $includePackage = $true } } + "RoleCapability" { if ($pkgRoleCapabilities ) { $includePackage = $true } } + "Workflow" { if ($pkgWorkflows ) { $includePackage = $true } } + } + } + + if (-not $includePackage) + { + break + } + } + } + } + } + + if ($includePackage) + { + $fastPackageReference = New-FastPackageReference -ProviderName $provider.ProviderName ` + -PackageName $pkg.Name ` + -Version $pkg.Version ` + -Source $Location ` + -ArtifactType $artifactType + + if($streamedResults -notcontains $fastPackageReference) + { + $streamedResults += $fastPackageReference + + $FromTrustedSource = $false + + $ModuleSourceName = Get-SourceName -Location $Location + + if($ModuleSourceName) + { + $FromTrustedSource = $script:PSGetModuleSources[$ModuleSourceName].Trusted + } + elseif($InstallationPolicy -eq "Trusted") + { + $FromTrustedSource = $true + } + + $sid = New-SoftwareIdentityFromPackage -Package $pkg ` + -PackageManagementProviderName $provider.ProviderName ` + -SourceLocation $Location ` + -IsFromTrustedSource:$FromTrustedSource ` + -Type $artifactType ` + -request $request + + $script:FastPackRefHastable[$fastPackageReference] = $pkg + + Write-Output -InputObject $sid + } + } + } + } + } + } + } +} + +function Download-Package +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $FastPackageReference, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Location + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Download-Package')) + + Install-PackageUtility -FastPackageReference $FastPackageReference -Request $Request -Location $Location +} + +function Install-Package +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $FastPackageReference + ) + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Install-Package')) + + Install-PackageUtility -FastPackageReference $FastPackageReference -Request $Request +} + +function Install-PackageUtility +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $FastPackageReference, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Location, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + $request + ) + + Set-ModuleSourcesVariable + + Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Install-PackageUtility')) + + Write-Debug ($LocalizedData.FastPackageReference -f $fastPackageReference) + + $Force = $false + $MinimumVersion = $null + $RequiredVersion = $null + $IsSavePackage = $false + $Scope = $null + $NoPathUpdate = $false + + # take the fastPackageReference and get the package object again. + $parts = $fastPackageReference -Split '[|]' + + if( $parts.Length -eq 5 ) + { + $providerName = $parts[0] + $packageName = $parts[1] + $version = $parts[2] + $sourceLocation= $parts[3] + $artfactType = $parts[4] + + # The default destination location for Modules and Scripts is ProgramFiles path + $scriptDestination = $script:ProgramFilesScriptsPath + $moduleDestination = $script:programFilesModulesPath + $Scope = 'AllUsers' + + if($artfactType -eq $script:PSArtifactTypeScript) + { + $AdminPreviligeErrorMessage = $LocalizedData.InstallScriptNeedsCurrentUserScopeParameterForNonAdminUser -f @($script:ProgramFilesScriptsPath, $script:MyDocumentsScriptsPath) + $AdminPreviligeErrorId = 'InstallScriptNeedsCurrentUserScopeParameterForNonAdminUser' + } + else + { + $AdminPreviligeErrorMessage = $LocalizedData.InstallModuleNeedsCurrentUserScopeParameterForNonAdminUser -f @($script:programFilesModulesPath, $script:MyDocumentsModulesPath) + $AdminPreviligeErrorId = 'InstallModuleNeedsCurrentUserScopeParameterForNonAdminUser' + } + + $installUpdate = $false + + $options = $request.Options + + if($options) + { + foreach( $o in $options.Keys ) + { + Write-Debug ("OPTION: {0} => {1}" -f ($o, $request.Options[$o]) ) + } + + if($options.ContainsKey('Scope')) + { + $Scope = $options['Scope'] + Write-Verbose ($LocalizedData.SpecifiedInstallationScope -f $Scope) + + if($Scope -eq "CurrentUser") + { + $scriptDestination = $script:MyDocumentsScriptsPath + $moduleDestination = $script:MyDocumentsModulesPath + } + elseif($Scope -eq "AllUsers") + { + $scriptDestination = $script:ProgramFilesScriptsPath + $moduleDestination = $script:programFilesModulesPath + + if(-not (Test-RunningAsElevated)) + { + # Throw an error when Install-Module/Script is used as a non-admin user and '-Scope CurrentUser' is not specified + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $AdminPreviligeErrorMessage ` + -ErrorId $AdminPreviligeErrorId ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + } + } + elseif($Location) + { + $IsSavePackage = $true + $Scope = $null + + $moduleDestination = $Location + $scriptDestination = $Location + } + # if no scope and no destination path and not elevated, then raise an error + elseif(-not (Test-RunningAsElevated)) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $AdminPreviligeErrorMessage ` + -ErrorId $AdminPreviligeErrorId ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + + if($options.ContainsKey('Force')) + { + $Force = $options['Force'] + + if($Force.GetType().ToString() -eq 'System.String') + { + if($Force -eq 'false') + { + $Force = $false + } + elseif($Force -eq 'true') + { + $Force = $true + } + } + } + + if($options.ContainsKey('NoPathUpdate')) + { + $NoPathUpdate = $options['NoPathUpdate'] + + if($NoPathUpdate.GetType().ToString() -eq 'System.String') + { + if($NoPathUpdate -eq 'false') + { + $NoPathUpdate = $false + } + elseif($NoPathUpdate -eq 'true') + { + $NoPathUpdate = $true + } + } + } + + if($options.ContainsKey('MinimumVersion')) + { + $MinimumVersion = $options['MinimumVersion'] + } + + if($options.ContainsKey('RequiredVersion')) + { + $RequiredVersion = $options['RequiredVersion'] + } + + if($options.ContainsKey('InstallUpdate')) + { + $installUpdate = $options['InstallUpdate'] + + if($installUpdate.GetType().ToString() -eq 'System.String') + { + if($installUpdate -eq 'false') + { + $installUpdate = $false + } + elseif($installUpdate -eq 'true') + { + $installUpdate = $true + } + } + } + + if($Scope -and ($artfactType -eq $script:PSArtifactTypeScript) -and (-not $installUpdate)) + { + ValidateAndSet-PATHVariableIfUserAccepts -Scope $Scope ` + -ScopePath $scriptDestination ` + -Request $request ` + -NoPathUpdate:$NoPathUpdate ` + -Force:$Force + } + + if($artfactType -eq $script:PSArtifactTypeModule) + { + $message = $LocalizedData.ModuleDestination -f @($moduleDestination) + } + else + { + $message = $LocalizedData.ScriptDestination -f @($scriptDestination, $moduleDestination) + } + Write-Verbose $message + } + + Write-Debug "ArtfactType is $artfactType" + + if($artfactType -eq $script:PSArtifactTypeModule) + { + # Test if module is already installed + $InstalledModuleInfo = if(-not $IsSavePackage){ Test-ModuleInstalled -Name $packageName -RequiredVersion $RequiredVersion } + + if(-not $Force -and $InstalledModuleInfo) + { + if($RequiredVersion -and (Test-ModuleSxSVersionSupport)) + { + # Check if the module with the required version is already installed otherwise proceed to install/update. + if($InstalledModuleInfo) + { + $message = $LocalizedData.ModuleWithRequiredVersionAlreadyInstalled -f ($InstalledModuleInfo.Version, $InstalledModuleInfo.Name, $InstalledModuleInfo.ModuleBase, $InstalledModuleInfo.Version) + Write-Error -Message $message -ErrorId "ModuleWithRequiredVersionAlreadyInstalled" -Category InvalidOperation + + return + } + } + else + { + if(-not $installUpdate) + { + if( (-not $MinimumVersion -and ($version -ne $InstalledModuleInfo.Version)) -or + ($MinimumVersion -and ($MinimumVersion -gt $InstalledModuleInfo.Version))) + { + if($PSVersionTable.PSVersion -ge [Version]"5.0") + { + $message = $LocalizedData.ModuleAlreadyInstalledSxS -f ($InstalledModuleInfo.Version, $InstalledModuleInfo.Name, $InstalledModuleInfo.ModuleBase, $version, $InstalledModuleInfo.Version, $version) + } + else + { + $message = $LocalizedData.ModuleAlreadyInstalled -f ($InstalledModuleInfo.Version, $InstalledModuleInfo.Name, $InstalledModuleInfo.ModuleBase, $InstalledModuleInfo.Version, $version) + } + Write-Error -Message $message -ErrorId "ModuleAlreadyInstalled" -Category InvalidOperation + } + else + { + $message = $LocalizedData.ModuleAlreadyInstalledVerbose -f ($InstalledModuleInfo.Version, $InstalledModuleInfo.Name, $InstalledModuleInfo.ModuleBase) + Write-Verbose $message + } + + return + } + else + { + if($InstalledModuleInfo.Version -lt $version) + { + $message = $LocalizedData.FoundModuleUpdate -f ($InstalledModuleInfo.Name, $version) + Write-Verbose $message + } + else + { + $message = $LocalizedData.NoUpdateAvailable -f ($InstalledModuleInfo.Name) + Write-Verbose $message + return + } + } + } + } + } + + if($artfactType -eq $script:PSArtifactTypeScript) + { + # Test if script is already installed + $InstalledScriptInfo = if(-not $IsSavePackage){ Test-ScriptInstalled -Name $packageName } + + Write-Debug "InstalledScriptInfo is $InstalledScriptInfo" + + if(-not $Force -and $InstalledScriptInfo) + { + if(-not $installUpdate) + { + if( (-not $MinimumVersion -and ($version -ne $InstalledScriptInfo.Version)) -or + ($MinimumVersion -and ($MinimumVersion -gt $InstalledScriptInfo.Version))) + { + $message = $LocalizedData.ScriptAlreadyInstalled -f ($InstalledScriptInfo.Version, $InstalledScriptInfo.Name, $InstalledScriptInfo.ScriptBase, $InstalledScriptInfo.Version, $version) + Write-Error -Message $message -ErrorId "ScriptAlreadyInstalled" -Category InvalidOperation + } + else + { + $message = $LocalizedData.ScriptAlreadyInstalledVerbose -f ($InstalledScriptInfo.Version, $InstalledScriptInfo.Name, $InstalledScriptInfo.ScriptBase) + Write-Verbose $message + } + + return + } + else + { + if($InstalledScriptInfo.Version -lt $version) + { + $message = $LocalizedData.FoundScriptUpdate -f ($InstalledScriptInfo.Name, $version) + Write-Verbose $message + } + else + { + $message = $LocalizedData.NoScriptUpdateAvailable -f ($InstalledScriptInfo.Name) + Write-Verbose $message + return + } + } + } + + # Throw an error if there is a command with the same name and -force is not specified. + if(-not $installUpdate -and + -not $IsSavePackage -and + -not $Force) + { + $cmd = Microsoft.PowerShell.Core\Get-Command -Name $packageName ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + if($cmd) + { + $message = $LocalizedData.CommandAlreadyAvailable -f ($packageName) + Write-Error -Message $message -ErrorId CommandAlreadyAvailableWitScriptName -Category InvalidOperation + return + } + } + } + + # create a temp folder and download the module + $tempDestination = Microsoft.PowerShell.Management\Join-Path -Path $script:TempPath -ChildPath "$(Microsoft.PowerShell.Utility\Get-Random)" + $null = Microsoft.PowerShell.Management\New-Item -Path $tempDestination -ItemType Directory -Force -Confirm:$false -WhatIf:$false + + try + { + $provider = $request.SelectProvider($providerName) + if(-not $provider) + { + Write-Error -Message ($LocalizedData.PackageManagementProviderIsNotAvailable -f $providerName) + + return + } + + if($request.IsCanceled) + { + return + } + + Write-Verbose ($LocalizedData.SpecifiedLocationAndOGP -f ($provider.ProviderName, $providerName)) + + $newRequest = New-Request -Options @{Destination=$tempDestination; + ExcludeVersion=$true} ` + -Sources @($SourceLocation) + + if($artfactType -eq $script:PSArtifactTypeModule) + { + $message = $LocalizedData.DownloadingModuleFromGallery -f ($packageName, $version, $sourceLocation) + } + else + { + $message = $LocalizedData.DownloadingScriptFromGallery -f ($packageName, $version, $sourceLocation) + } + Write-Verbose $message + + $installedPkgs = $provider.InstallPackage($script:FastPackRefHastable[$fastPackageReference], $newRequest) + + foreach($pkg in $installedPkgs) + { + if($request.IsCanceled) + { + return + } + + $destinationModulePath = Microsoft.PowerShell.Management\Join-Path -Path $moduleDestination -ChildPath $pkg.Name + + # Side-by-Side module version is avialable on PowerShell 5.0 or later versions only + # By default, PowerShell module versions will be installed/updated Side-by-Side. + if(Test-ModuleSxSVersionSupport) + { + $destinationModulePath = Microsoft.PowerShell.Management\Join-Path -Path $destinationModulePath -ChildPath $pkg.Version + } + + $destinationscriptPath = $scriptDestination + + # Get actual artifact type from the package + $packageType = $script:PSArtifactTypeModule + $installLocation = $destinationModulePath + $tempPackagePath = Microsoft.PowerShell.Management\Join-Path -Path $tempDestination -ChildPath $pkg.Name + if(Microsoft.PowerShell.Management\Test-Path -Path $tempPackagePath) + { + $packageFiles = Microsoft.PowerShell.Management\Get-ChildItem -Path $tempPackagePath -Recurse -Exclude "*.nupkg","*.nuspec" + + if($packageFiles -and $packageFiles.GetType().ToString() -eq 'System.IO.FileInfo' -and $packageFiles.Name -eq "$($pkg.Name).ps1") + { + $packageType = $script:PSArtifactTypeScript + $installLocation = $destinationscriptPath + } + } + + $AdditionalParams = @{} + + if(-not $IsSavePackage) + { + # During the install operation: + # InstalledDate should be the current Get-Date value + # UpdatedDate should be null + # + # During the update operation: + # InstalledDate should be from the previous version's InstalledDate otherwise current Get-Date value + # UpdatedDate should be the current Get-Date value + # + $InstalledDate = Microsoft.PowerShell.Utility\Get-Date + + if($installUpdate) + { + $AdditionalParams['UpdatedDate'] = Microsoft.PowerShell.Utility\Get-Date + + $InstalledItemDetails = $null + if($packageType -eq $script:PSArtifactTypeModule) + { + $InstalledItemDetails = Get-InstalledModuleDetails -Name $pkg.Name | Select-Object -Last 1 + } + elseif($packageType -eq $script:PSArtifactTypeScript) + { + $InstalledItemDetails = Get-InstalledScriptDetails -Name $pkg.Name | Select-Object -Last 1 + } + + if($InstalledItemDetails -and + $InstalledItemDetails.PSGetItemInfo -and + (Get-Member -InputObject $InstalledItemDetails.PSGetItemInfo -Name 'InstalledDate') -and + $InstalledItemDetails.PSGetItemInfo.InstalledDate) + { + $InstalledDate = $InstalledItemDetails.PSGetItemInfo.InstalledDate + } + } + + $AdditionalParams['InstalledDate'] = $InstalledDate + } + + $sid = New-SoftwareIdentityFromPackage -Package $pkg ` + -SourceLocation $sourceLocation ` + -PackageManagementProviderName $provider.ProviderName ` + -Request $request ` + -Type $packageType ` + -InstalledLocation $installLocation ` + @AdditionalParams + + # construct the PSGetItemInfo from SoftwareIdentity and persist it + $psgItemInfo = New-PSGetItemInfo -SoftwareIdentity $pkg ` + -PackageManagementProviderName $provider.ProviderName ` + -SourceLocation $sourceLocation ` + -Type $packageType ` + -InstalledLocation $installLocation ` + @AdditionalParams + + if($packageType -eq $script:PSArtifactTypeModule) + { + if ($psgItemInfo.PowerShellGetFormatVersion -and + ($script:SupportedPSGetFormatVersionMajors -notcontains $psgItemInfo.PowerShellGetFormatVersion.Major)) + { + $message = $LocalizedData.NotSupportedPowerShellGetFormatVersion -f ($psgItemInfo.Name, $psgItemInfo.PowerShellGetFormatVersion, $psgItemInfo.Name) + Write-Error -Message $message -ErrorId "NotSupportedPowerShellGetFormatVersion" -Category InvalidOperation + continue + } + + if(-not $psgItemInfo.PowerShellGetFormatVersion) + { + $sourceModulePath = Microsoft.PowerShell.Management\Join-Path $tempDestination $pkg.Name + } + else + { + $sourceModulePath = Microsoft.PowerShell.Management\Join-Path $tempDestination "$($pkg.Name)\Content\*\$script:ModuleReferences\$($pkg.Name)" + } + + $CurrentModuleInfo = $null + + # Validate the module + if(-not $IsSavePackage) + { + $CurrentModuleInfo = Test-ValidManifestModule -ModuleBasePath $sourceModulePath + + if(-not $CurrentModuleInfo) + { + $message = $LocalizedData.InvalidPSModule -f ($pkg.Name) + Write-Error -Message $message -ErrorId "InvalidManifestModule" -Category InvalidOperation + continue + } + } + + # Test if module is already installed + $InstalledModuleInfo2 = if(-not $IsSavePackage){ Test-ModuleInstalled -Name $pkg.Name -RequiredVersion $pkg.Version } + + if($pkg.Name -ne $packageName) + { + if(-not $Force -and $InstalledModuleInfo2) + { + if(Test-ModuleSxSVersionSupport) + { + if($pkg.version -eq $InstalledModuleInfo2.Version) + { + if(-not $installUpdate) + { + $message = $LocalizedData.ModuleWithRequiredVersionAlreadyInstalled -f ($InstalledModuleInfo2.Version, $InstalledModuleInfo2.Name, $InstalledModuleInfo2.ModuleBase, $InstalledModuleInfo2.Version) + } + else + { + $message = $LocalizedData.NoUpdateAvailable -f ($pkg.Name) + } + + Write-Verbose $message + Continue + } + } + else + { + if(-not $installUpdate) + { + $message = $LocalizedData.ModuleAlreadyInstalledVerbose -f ($InstalledModuleInfo2.Version, $InstalledModuleInfo2.Name, $InstalledModuleInfo2.ModuleBase) + Write-Verbose $message + Continue + } + else + { + if($pkg.version -gt $InstalledModuleInfo2.Version) + { + $message = $LocalizedData.FoundModuleUpdate -f ($pkg.Name, $pkg.Version) + Write-Verbose $message + } + else + { + $message = $LocalizedData.NoUpdateAvailable -f ($pkg.Name) + Write-Verbose $message + Continue + } + } + } + } + + if($IsSavePackage) + { + $DependencyInstallMessage = $LocalizedData.SavingDependencyModule -f ($pkg.Name, $pkg.Version, $packageName) + } + else + { + $DependencyInstallMessage = $LocalizedData.InstallingDependencyModule -f ($pkg.Name, $pkg.Version, $packageName) + } + + Write-Verbose $DependencyInstallMessage + } + + # check if module is in use + if($InstalledModuleInfo2) + { + $moduleInUse = Test-ModuleInUse -ModuleBasePath $InstalledModuleInfo2.ModuleBase ` + -ModuleName $InstalledModuleInfo2.Name ` + -ModuleVersion $InstalledModuleInfo2.Version ` + -Verbose:$VerbosePreference ` + -WarningAction $WarningPreference ` + -ErrorAction $ErrorActionPreference ` + -Debug:$DebugPreference + + if($moduleInUse) + { + $message = $LocalizedData.ModuleIsInUse -f ($psgItemInfo.Name) + Write-Verbose $message + continue + } + } + + Copy-Module -SourcePath $sourceModulePath -DestinationPath $destinationModulePath -PSGetItemInfo $psgItemInfo + + if(-not $IsSavePackage) + { + # Write warning messages if externally managed module dependencies are not installed. + $ExternalModuleDependencies = Get-ExternalModuleDependencies -PSModuleInfo $CurrentModuleInfo + foreach($ExternalDependency in $ExternalModuleDependencies) + { + $depModuleInfo = Test-ModuleInstalled -Name $ExternalDependency + + if(-not $depModuleInfo) + { + Write-Warning -Message ($LocalizedData.MissingExternallyManagedModuleDependency -f $ExternalDependency,$pkg.Name,$ExternalDependency) + } + else + { + Write-Verbose -Message ($LocalizedData.ExternallyManagedModuleDependencyIsInstalled -f $ExternalDependency) + } + } + } + + # Remove the old module base folder if it is different from the required destination module path when -Force is specified + if($Force -and + $InstalledModuleInfo2 -and + -not $destinationModulePath.StartsWith($InstalledModuleInfo2.ModuleBase, [System.StringComparison]::OrdinalIgnoreCase)) + { + Microsoft.PowerShell.Management\Remove-Item -Path $InstalledModuleInfo2.ModuleBase ` + -Force -Recurse ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false -WhatIf:$false + } + + if($IsSavePackage) + { + $message = $LocalizedData.ModuleSavedSuccessfully -f ($psgItemInfo.Name) + } + else + { + $message = $LocalizedData.ModuleInstalledSuccessfully -f ($psgItemInfo.Name) + } + Write-Verbose $message + } + + + if($packageType -eq $script:PSArtifactTypeScript) + { + if ($psgItemInfo.PowerShellGetFormatVersion -and + ($script:SupportedPSGetFormatVersionMajors -notcontains $psgItemInfo.PowerShellGetFormatVersion.Major)) + { + $message = $LocalizedData.NotSupportedPowerShellGetFormatVersionScripts -f ($psgItemInfo.Name, $psgItemInfo.PowerShellGetFormatVersion, $psgItemInfo.Name) + Write-Error -Message $message -ErrorId "NotSupportedPowerShellGetFormatVersion" -Category InvalidOperation + continue + } + + $sourceScriptPath = Microsoft.PowerShell.Management\Join-Path -Path $tempPackagePath -ChildPath "$($pkg.Name).ps1" + + $currentScriptInfo = $null + if(-not $IsSavePackage) + { + # Validate the script + $currentScriptInfo = Test-ScriptFileInfo -Path $sourceScriptPath -ErrorAction SilentlyContinue + + if(-not $currentScriptInfo) + { + $message = $LocalizedData.InvalidPowerShellScriptFile -f ($pkg.Name) + Write-Error -Message $message -ErrorId "InvalidPowerShellScriptFile" -Category InvalidOperation -TargetObject $pkg.Name + continue + } + } + + # Test if script is already installed + $InstalledScriptInfo2 = if(-not $IsSavePackage){ Test-ScriptInstalled -Name $pkg.Name } + + if($pkg.Name -ne $packageName) + { + if(-not $Force -and $InstalledScriptInfo2) + { + if(-not $installUpdate) + { + $message = $LocalizedData.ScriptAlreadyInstalledVerbose -f ($InstalledScriptInfo2.Version, $InstalledScriptInfo2.Name, $InstalledScriptInfo2.ScriptBase) + Write-Verbose $message + Continue + } + else + { + if($pkg.version -gt $InstalledScriptInfo2.Version) + { + $message = $LocalizedData.FoundScriptUpdate -f ($pkg.Name, $pkg.Version) + Write-Verbose $message + } + else + { + $message = $LocalizedData.NoScriptUpdateAvailable -f ($pkg.Name) + Write-Verbose $message + Continue + } + } + } + + if($IsSavePackage) + { + $DependencyInstallMessage = $LocalizedData.SavingDependencyScript -f ($pkg.Name, $pkg.Version, $packageName) + } + else + { + $DependencyInstallMessage = $LocalizedData.InstallingDependencyScript -f ($pkg.Name, $pkg.Version, $packageName) + } + + Write-Verbose $DependencyInstallMessage + } + + Write-Debug "SourceScriptPath is $sourceScriptPath and DestinationscriptPath is $destinationscriptPath" + Copy-ScriptFile -SourcePath $sourceScriptPath -DestinationPath $destinationscriptPath -PSGetItemInfo $psgItemInfo -Scope $Scope + + if(-not $IsSavePackage) + { + # Write warning messages if externally managed module dependencies are not installed. + foreach($ExternalDependency in $currentScriptInfo.ExternalModuleDependencies) + { + $depModuleInfo = Test-ModuleInstalled -Name $ExternalDependency + + if(-not $depModuleInfo) + { + Write-Warning -Message ($LocalizedData.ScriptMissingExternallyManagedModuleDependency -f $ExternalDependency,$pkg.Name,$ExternalDependency) + } + else + { + Write-Verbose -Message ($LocalizedData.ExternallyManagedModuleDependencyIsInstalled -f $ExternalDependency) + } + } + + # Write warning messages if externally managed script dependencies are not installed. + foreach($ExternalDependency in $currentScriptInfo.ExternalScriptDependencies) + { + $depScriptInfo = Test-ScriptInstalled -Name $ExternalDependency + + if(-not $depScriptInfo) + { + Write-Warning -Message ($LocalizedData.ScriptMissingExternallyManagedScriptDependency -f $ExternalDependency,$pkg.Name,$ExternalDependency) + } + else + { + Write-Verbose -Message ($LocalizedData.ScriptExternallyManagedScriptDependencyIsInstalled -f $ExternalDependency) + } + } + } + + # Remove the old scriptfile if it's path different from the required destination script path when -Force is specified + if($Force -and + $InstalledScriptInfo2 -and + -not $destinationscriptPath.StartsWith($InstalledScriptInfo2.ScriptBase, [System.StringComparison]::OrdinalIgnoreCase)) + { + Microsoft.PowerShell.Management\Remove-Item -Path $InstalledScriptInfo2.Path ` + -Force ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false -WhatIf:$false + } + + if($IsSavePackage) + { + $message = $LocalizedData.ScriptSavedSuccessfully -f ($psgItemInfo.Name) + } + else + { + $message = $LocalizedData.ScriptInstalledSuccessfully -f ($psgItemInfo.Name) + } + Write-Verbose $message + } + + Write-Output -InputObject $sid + } + } + finally + { + Microsoft.PowerShell.Management\Remove-Item $tempDestination -Force -Recurse -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } + } +} + +function Uninstall-Package +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $fastPackageReference + ) + + Write-Debug -Message ($LocalizedData.ProviderApiDebugMessage -f ('Uninstall-Package')) + + Write-Debug -Message ($LocalizedData.FastPackageReference -f $fastPackageReference) + + # take the fastPackageReference and get the package object again. + $parts = $fastPackageReference -Split '[|]' + $Force = $false + + $options = $request.Options + if($options) + { + foreach( $o in $options.Keys ) + { + Write-Debug -Message ("OPTION: {0} => {1}" -f ($o, $request.Options[$o]) ) + } + } + + if($parts.Length -eq 5) + { + $providerName = $parts[0] + $packageName = $parts[1] + $version = $parts[2] + $sourceLocation= $parts[3] + $artfactType = $parts[4] + + if($request.IsCanceled) + { + return + } + + if($options.ContainsKey('Force')) + { + $Force = $options['Force'] + + if($Force.GetType().ToString() -eq 'System.String') + { + if($Force -eq 'false') + { + $Force = $false + } + elseif($Force -eq 'true') + { + $Force = $true + } + } + } + + if($artfactType -eq $script:PSArtifactTypeModule) + { + $moduleName = $packageName + $InstalledModuleInfo = $script:PSGetInstalledModules["$($moduleName)$($version)"] + + if(-not $InstalledModuleInfo) + { + $message = $LocalizedData.ModuleUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet -f $moduleName + + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "ModuleUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + + return + } + + $moduleBase = $InstalledModuleInfo.PSGetItemInfo.InstalledLocation + + if(-not (Test-RunningAsElevated) -and $moduleBase.StartsWith($script:programFilesModulesPath, [System.StringComparison]::OrdinalIgnoreCase)) + { + $message = $LocalizedData.AdminPrivilegesRequiredForUninstall -f ($moduleName, $moduleBase) + + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "AdminPrivilegesRequiredForUninstall" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + + return + } + + $dependentModuleScript = { + param ([string] $moduleName) + Microsoft.PowerShell.Core\Get-Module -ListAvailable | + Microsoft.PowerShell.Core\Where-Object { + ($moduleName -ne $_.Name) -and ( + ($_.RequiredModules -and $_.RequiredModules.Name -contains $moduleName) -or + ($_.NestedModules -and $_.NestedModules.Name -contains $moduleName)) + } + } + $dependentModulesJob = Microsoft.PowerShell.Core\Start-Job -ScriptBlock $dependentModuleScript -ArgumentList $moduleName + Microsoft.PowerShell.Core\Wait-Job -job $dependentModulesJob + $dependentModules = Microsoft.PowerShell.Core\Receive-Job -job $dependentModulesJob + + if(-not $Force -and $dependentModules) + { + $message = $LocalizedData.UnableToUninstallAsOtherModulesNeedThisModule -f ($moduleName, $version, $moduleBase, $(($dependentModules.Name | Select-Object -Unique) -join ','), $moduleName) + + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "UnableToUninstallAsOtherModulesNeedThisModule" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + + return + } + + $moduleInUse = Test-ModuleInUse -ModuleBasePath $moduleBase ` + -ModuleName $InstalledModuleInfo.PSGetItemInfo.Name` + -ModuleVersion $InstalledModuleInfo.PSGetItemInfo.Version ` + -Verbose:$VerbosePreference ` + -WarningAction $WarningPreference ` + -ErrorAction $ErrorActionPreference ` + -Debug:$DebugPreference + + if($moduleInUse) + { + $message = $LocalizedData.ModuleIsInUse -f ($moduleName) + + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "ModuleIsInUse" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + + return + } + + $ModuleBaseFolderToBeRemoved = $moduleBase + + # With SxS version support, more than one version of the module can be installed. + # - Remove the parent directory of the module version base when only one version is installed + # - Don't remove the modulebase when it was installed before SxS version support and + # other versions are installed under the module base folder + # + if(Test-ModuleSxSVersionSupport) + { + $ModuleBaseWithoutVersion = $moduleBase + $IsModuleInstalledAsSxSVersion = $false + + if($moduleBase.EndsWith("$version", [System.StringComparison]::OrdinalIgnoreCase)) + { + $IsModuleInstalledAsSxSVersion = $true + $ModuleBaseWithoutVersion = Microsoft.PowerShell.Management\Split-Path -Path $moduleBase -Parent + } + + $InstalledVersionsWithSameModuleBase = @() + Get-Module -Name $moduleName -ListAvailable | + Microsoft.PowerShell.Core\ForEach-Object { + if($_.ModuleBase.StartsWith($ModuleBaseWithoutVersion, [System.StringComparison]::OrdinalIgnoreCase)) + { + $InstalledVersionsWithSameModuleBase += $_.ModuleBase + } + } + + # Remove ..\ModuleName directory when only one module is installed with the same ..\ModuleName path + # like ..\ModuleName\1.0 or ..\ModuleName + if($InstalledVersionsWithSameModuleBase.Count -eq 1) + { + $ModuleBaseFolderToBeRemoved = $ModuleBaseWithoutVersion + } + elseif($ModuleBaseWithoutVersion -eq $moduleBase) + { + # There are version specific folders under the same module base dir + # Throw an error saying uninstall other versions then uninstall this current version + $message = $LocalizedData.UnableToUninstallModuleVersion -f ($moduleName, $version, $moduleBase) + + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "UnableToUninstallModuleVersion" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + + return + } + # Otherwise specified version folder will be removed as current module base is assigned to $ModuleBaseFolderToBeRemoved + } + + Microsoft.PowerShell.Management\Remove-Item -Path $ModuleBaseFolderToBeRemoved ` + -Force -Recurse ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false -WhatIf:$false + + $message = $LocalizedData.ModuleUninstallationSucceeded -f $moduleName, $moduleBase + Write-Verbose $message + + Write-Output -InputObject $InstalledModuleInfo.SoftwareIdentity + } + elseif($artfactType -eq $script:PSArtifactTypeScript) + { + $scriptName = $packageName + $InstalledScriptInfo = $script:PSGetInstalledScripts["$($scriptName)$($version)"] + + if(-not $InstalledScriptInfo) + { + $message = $LocalizedData.ScriptUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet -f $scriptName + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "ScriptUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + + return + } + + $scriptBase = $InstalledScriptInfo.PSGetItemInfo.InstalledLocation + $installedScriptInfoPath = $script:MyDocumentsInstalledScriptInfosPath + + if($scriptBase.StartsWith($script:ProgramFilesScriptsPath, [System.StringComparison]::OrdinalIgnoreCase)) + { + if(-not (Test-RunningAsElevated)) + { + $message = $LocalizedData.AdminPrivilegesRequiredForScriptUninstall -f ($scriptName, $scriptBase) + + ThrowError -ExceptionName "System.InvalidOperationException" ` + -ExceptionMessage $message ` + -ErrorId "AdminPrivilegesRequiredForUninstall" ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + + return + } + + $installedScriptInfoPath = $script:ProgramFilesInstalledScriptInfosPath + } + + # Check if there are any dependent scripts + $dependentScriptDetails = $script:PSGetInstalledScripts.Values | + Microsoft.PowerShell.Core\Where-Object { + $_.PSGetItemInfo.Dependencies -contains $scriptName + } + + $dependentScriptNames = $dependentScriptDetails | + Microsoft.PowerShell.Core\ForEach-Object { $_.PSGetItemInfo.Name } + + if(-not $Force -and $dependentScriptNames) + { + $message = $LocalizedData.UnableToUninstallAsOtherScriptsNeedThisScript -f + ($scriptName, + $version, + $scriptBase, + $(($dependentScriptNames | Select-Object -Unique) -join ','), + $scriptName) + + ThrowError -ExceptionName 'System.InvalidOperationException' ` + -ExceptionMessage $message ` + -ErrorId 'UnableToUninstallAsOtherScriptsNeedThisScript' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidOperation + return + } + + $scriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $scriptBase ` + -ChildPath "$($scriptName).ps1" + + $installledScriptInfoFilePath = Microsoft.PowerShell.Management\Join-Path -Path $installedScriptInfoPath ` + -ChildPath "$($scriptName)_$($script:InstalledScriptInfoFileName)" + + # Remove the script file and it's corresponding InstalledScriptInfo.xml + if(Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf) + { + Microsoft.PowerShell.Management\Remove-Item -Path $scriptFilePath ` + -Force ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false -WhatIf:$false + } + + if(Microsoft.PowerShell.Management\Test-Path -Path $installledScriptInfoFilePath -PathType Leaf) + { + Microsoft.PowerShell.Management\Remove-Item -Path $installledScriptInfoFilePath ` + -Force ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false -WhatIf:$false + } + + $message = $LocalizedData.ScriptUninstallationSucceeded -f $scriptName, $scriptBase + Write-Verbose $message + + Write-Output -InputObject $InstalledScriptInfo.SoftwareIdentity + } + } +} + +function Get-InstalledPackage +{ + [CmdletBinding()] + param + ( + [Parameter()] + [string] + $Name, + + [Parameter()] + [Version] + $RequiredVersion, + + [Parameter()] + [Version] + $MinimumVersion, + + [Parameter()] + [Version] + $MaximumVersion + ) + + Write-Debug -Message ($LocalizedData.ProviderApiDebugMessage -f ('Get-InstalledPackage')) + + $options = $request.Options + + foreach( $o in $options.Keys ) + { + Write-Debug ( "OPTION: {0} => {1}" -f ($o, $options[$o]) ) + } + + $artifactTypes = $script:PSArtifactTypeModule + if($options.ContainsKey($script:PSArtifactType)) + { + $artifactTypes = $options[$script:PSArtifactType] + } + + if($artifactTypes -eq $script:All) + { + $artifactTypes = @($script:PSArtifactTypeModule,$script:PSArtifactTypeScript) + } + + if($artifactTypes -contains $script:PSArtifactTypeModule) + { + Get-InstalledModuleDetails -Name $Name ` + -RequiredVersion $RequiredVersion ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion | Microsoft.PowerShell.Core\ForEach-Object {$_.SoftwareIdentity} + } + + if($artifactTypes -contains $script:PSArtifactTypeScript) + { + Get-InstalledScriptDetails -Name $Name ` + -RequiredVersion $RequiredVersion ` + -MinimumVersion $MinimumVersion ` + -MaximumVersion $MaximumVersion | Microsoft.PowerShell.Core\ForEach-Object {$_.SoftwareIdentity} + } +} + +#endregion + +#region Internal Utility functions for the PackageManagement Provider Implementation + +function Set-InstalledScriptsVariable +{ + # Initialize list of scripts installed by the PowerShellGet provider + $script:PSGetInstalledScripts = [ordered]@{} + $scriptPaths = @($script:ProgramFilesInstalledScriptInfosPath, $script:MyDocumentsInstalledScriptInfosPath) + + foreach ($location in $scriptPaths) + { + # find all scripts installed using PowerShellGet + $scriptInfoFiles = Get-ChildItem -Path $location ` + -Filter "*$script:InstalledScriptInfoFileName" ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + + if($scriptInfoFiles) + { + foreach ($scriptInfoFile in $scriptInfoFiles) + { + $psgetItemInfo = DeSerialize-PSObject -Path $scriptInfoFile.FullName + + $scriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $psgetItemInfo.InstalledLocation ` + -ChildPath "$($psgetItemInfo.Name).ps1" + + # Remove the InstalledScriptInfo.xml file if the actual script file was manually uninstalled by the user + if(-not (Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf)) + { + Microsoft.PowerShell.Management\Remove-Item -Path $scriptInfoFile.FullName -Force -ErrorAction SilentlyContinue + + continue + } + + $package = New-SoftwareIdentityFromPSGetItemInfo -PSGetItemInfo $psgetItemInfo + + if($package) + { + $script:PSGetInstalledScripts["$($psgetItemInfo.Name)$($psgetItemInfo.Version)"] = @{ + SoftwareIdentity = $package + PSGetItemInfo = $psgetItemInfo + } + } + } + } + } +} + +function Get-InstalledScriptDetails +{ + [CmdletBinding()] + param + ( + [Parameter()] + [string] + $Name, + + [Parameter()] + [Version] + $RequiredVersion, + + [Parameter()] + [Version] + $MinimumVersion, + + [Parameter()] + [Version] + $MaximumVersion + ) + + Set-InstalledScriptsVariable + + # Keys in $script:PSGetInstalledScripts are "", + # first filter the installed scripts using "$Name*" wildcard search + # then apply $Name wildcard search to get the script name which meets the specified name with wildcards. + # + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern "$Name*",$script:wildcardOptions + $nameWildcardPattern = New-Object System.Management.Automation.WildcardPattern $Name,$script:wildcardOptions + + $script:PSGetInstalledScripts.GetEnumerator() | Microsoft.PowerShell.Core\ForEach-Object { + if($wildcardPattern.IsMatch($_.Key)) + { + $InstalledScriptDetails = $_.Value + + if(-not $Name -or $nameWildcardPattern.IsMatch($InstalledScriptDetails.PSGetItemInfo.Name)) + { + if($RequiredVersion) + { + if($RequiredVersion -eq $InstalledScriptDetails.PSGetItemInfo.Version) + { + $InstalledScriptDetails + } + } + else + { + if( (-not $MinimumVersion -or ($MinimumVersion -le $InstalledScriptDetails.PSGetItemInfo.Version)) -and + (-not $MaximumVersion -or ($MaximumVersion -ge $InstalledScriptDetails.PSGetItemInfo.Version))) + { + $InstalledScriptDetails + } + } + } + } + } +} + +function Get-InstalledModuleDetails +{ + [CmdletBinding()] + param + ( + [Parameter()] + [string] + $Name, + + [Parameter()] + [Version] + $RequiredVersion, + + [Parameter()] + [Version] + $MinimumVersion, + + [Parameter()] + [Version] + $MaximumVersion + ) + + Set-InstalledModulesVariable + + # Keys in $script:PSGetInstalledModules are "", + # first filter the installed modules using "$Name*" wildcard search + # then apply $Name wildcard search to get the module name which meets the specified name with wildcards. + # + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern "$Name*",$script:wildcardOptions + $nameWildcardPattern = New-Object System.Management.Automation.WildcardPattern $Name,$script:wildcardOptions + + $script:PSGetInstalledModules.GetEnumerator() | Microsoft.PowerShell.Core\ForEach-Object { + if($wildcardPattern.IsMatch($_.Key)) + { + $InstalledModuleDetails = $_.Value + + if(-not $Name -or $nameWildcardPattern.IsMatch($InstalledModuleDetails.PSGetItemInfo.Name)) + { + if($RequiredVersion) + { + if($RequiredVersion -eq $InstalledModuleDetails.PSGetItemInfo.Version) + { + $InstalledModuleDetails + } + } + else + { + if( (-not $MinimumVersion -or ($MinimumVersion -le $InstalledModuleDetails.PSGetItemInfo.Version)) -and + (-not $MaximumVersion -or ($MaximumVersion -ge $InstalledModuleDetails.PSGetItemInfo.Version))) + { + $InstalledModuleDetails + } + } + } + } + } +} + +function New-SoftwareIdentityFromPackage +{ + param + ( + [Parameter(Mandatory=$true)] + $Package, + + [Parameter(Mandatory=$true)] + [string] + $PackageManagementProviderName, + + [Parameter(Mandatory=$true)] + [string] + $SourceLocation, + + [Parameter()] + [switch] + $IsFromTrustedSource, + + [Parameter(Mandatory=$true)] + $request, + + [Parameter(Mandatory=$true)] + [string] + $Type, + + [Parameter()] + [string] + $InstalledLocation, + + [Parameter()] + [System.DateTime] + $InstalledDate, + + [Parameter()] + [System.DateTime] + $UpdatedDate + ) + + $fastPackageReference = New-FastPackageReference -ProviderName $PackageManagementProviderName ` + -PackageName $Package.Name ` + -Version $Package.Version ` + -Source $SourceLocation ` + -ArtifactType $Type + + $links = New-Object -TypeName System.Collections.ArrayList + foreach($lnk in $Package.Links) + { + if( $lnk.Relationship -eq "icon" -or $lnk.Relationship -eq "license" -or $lnk.Relationship -eq "project" ) + { + $links.Add( (New-Link -Href $lnk.HRef -RelationShip $lnk.Relationship ) ) + } + } + + $entities = New-Object -TypeName System.Collections.ArrayList + foreach( $entity in $Package.Entities ) + { + if( $entity.Role -eq "author" -or $entity.Role -eq "owner" ) + { + $entities.Add( (New-Entity -Name $entity.Name -Role $entity.Role -RegId $entity.RegId -Thumbprint $entity.Thumbprint) ) + } + } + + $deps = (new-Object -TypeName System.Collections.ArrayList) + foreach( $dep in $pkg.Dependencies ) + { + # Add each dependency and say it's from this provider. + $newDep = New-Dependency -ProviderName $script:PSModuleProviderName ` + -PackageName $request.Services.ParsePackageName($dep) ` + -Version $request.Services.ParsePackageVersion($dep) ` + -Source $SourceLocation + + $deps.Add( $newDep ) + } + + + $details = New-Object -TypeName System.Collections.Hashtable + + foreach ( $key in $Package.Metadata.Keys.LocalName) + { + if (!$details.ContainsKey($key)) + { + $details.Add($key, (Get-First $Package.Metadata[$key]) ) + } + } + + $details.Add( "PackageManagementProvider" , $PackageManagementProviderName ) + $details.Add( "Type" , $Type ) + + if($InstalledLocation) + { + $details.Add( $script:InstalledLocation , $InstalledLocation ) + } + + if($InstalledDate) + { + $details.Add( 'installeddate' , $InstalledDate.ToString() ) + } + + if($UpdatedDate) + { + $details.Add( 'updateddate' , $UpdatedDate.ToString() ) + } + + # Initialize package source name to the source location + $sourceNameForSoftwareIdentity = $SourceLocation + + $sourceName = (Get-SourceName -Location $SourceLocation) + + if($sourceName) + { + $details.Add( "SourceName" , $sourceName ) + + # Override the source name only if we are able to map source location to source name + $sourceNameForSoftwareIdentity = $sourceName + } + + $params = @{FastPackageReference = $fastPackageReference; + Name = $Package.Name; + Version = $Package.Version; + versionScheme = "MultiPartNumeric"; + Source = $sourceNameForSoftwareIdentity; + Summary = $Package.Summary; + SearchKey = $Package.Name; + FullPath = $Package.FullPath; + FileName = $Package.Name; + Details = $details; + Entities = $entities; + Links = $links; + Dependencies = $deps; + } + + if($IsFromTrustedSource) + { + $params["FromTrustedSource"] = $true + } + + $sid = New-SoftwareIdentity @params + + return $sid +} + +function New-PackageSourceFromModuleSource +{ + param + ( + [Parameter(Mandatory=$true)] + $ModuleSource + ) + + $ScriptSourceLocation = $null + if(Get-Member -InputObject $ModuleSource -Name $script:ScriptSourceLocation) + { + $ScriptSourceLocation = $ModuleSource.ScriptSourceLocation + } + + $ScriptPublishLocation = $ModuleSource.PublishLocation + if(Get-Member -InputObject $ModuleSource -Name $script:ScriptPublishLocation) + { + $ScriptPublishLocation = $ModuleSource.ScriptPublishLocation + } + + $packageSourceDetails = @{} + $packageSourceDetails["InstallationPolicy"] = $ModuleSource.InstallationPolicy + $packageSourceDetails["PackageManagementProvider"] = (Get-ProviderName -PSCustomObject $ModuleSource) + $packageSourceDetails[$script:PublishLocation] = $ModuleSource.PublishLocation + $packageSourceDetails[$script:ScriptSourceLocation] = $ScriptSourceLocation + $packageSourceDetails[$script:ScriptPublishLocation] = $ScriptPublishLocation + + $ModuleSource.ProviderOptions.GetEnumerator() | Microsoft.PowerShell.Core\ForEach-Object { + $packageSourceDetails[$_.Key] = $_.Value + } + + # create a new package source + $src = New-PackageSource -Name $ModuleSource.Name ` + -Location $ModuleSource.SourceLocation ` + -Trusted $ModuleSource.Trusted ` + -Registered $ModuleSource.Registered ` + -Details $packageSourceDetails + + Write-Verbose ( $LocalizedData.RepositoryDetails -f ($src.Name, $src.Location, $src.IsTrusted, $src.IsRegistered) ) + + # return the package source object. + Write-Output -InputObject $src +} + +function New-ModuleSourceFromPackageSource +{ + param + ( + [Parameter(Mandatory=$true)] + $PackageSource + ) + + $moduleSource = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + Name = $PackageSource.Name + SourceLocation = $PackageSource.Location + Trusted=$PackageSource.IsTrusted + Registered=$PackageSource.IsRegistered + InstallationPolicy = $PackageSource.Details['InstallationPolicy'] + PackageManagementProvider=$PackageSource.Details['PackageManagementProvider'] + PublishLocation=$PackageSource.Details[$script:PublishLocation] + ScriptSourceLocation=$PackageSource.Details[$script:ScriptSourceLocation] + ScriptPublishLocation=$PackageSource.Details[$script:ScriptPublishLocation] + ProviderOptions = @{} + }) + + $PackageSource.Details.GetEnumerator() | Microsoft.PowerShell.Core\ForEach-Object { + if($_.Key -ne 'PackageManagementProvider' -and + $_.Key -ne $script:PublishLocation -and + $_.Key -ne $script:ScriptPublishLocation -and + $_.Key -ne $script:ScriptSourceLocation -and + $_.Key -ne 'InstallationPolicy') + { + $moduleSource.ProviderOptions[$_.Key] = $_.Value + } + } + + $moduleSource.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.PSRepository") + + # return the module source object. + Write-Output -InputObject $moduleSource +} + +function New-FastPackageReference +{ + param + ( + [Parameter(Mandatory=$true)] + [string] + $ProviderName, + + [Parameter(Mandatory=$true)] + [string] + $PackageName, + + [Parameter(Mandatory=$true)] + [string] + $Version, + + [Parameter(Mandatory=$true)] + [string] + $Source, + + [Parameter(Mandatory=$true)] + [string] + $ArtifactType + ) + + return "$ProviderName|$PackageName|$Version|$Source|$ArtifactType" +} + +function Get-First +{ + param + ( + [Parameter(Mandatory=$true)] + $IEnumerator + ) + + foreach($item in $IEnumerator) + { + return $item + } + + return $null +} + +function Set-InstalledModulesVariable +{ + # Initialize list of modules installed by the PowerShellGet provider + $script:PSGetInstalledModules = [ordered]@{} + + $modulePaths = @($script:ProgramFilesModulesPath, $script:MyDocumentsModulesPath) + + foreach ($location in $modulePaths) + { + # find all modules installed using PowerShellGet + $moduleBases = Get-ChildItem $location -Recurse ` + -Attributes Hidden -Filter $script:PSGetItemInfoFileName ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + | Foreach-Object { $_.Directory } + + + foreach ($moduleBase in $moduleBases) + { + $PSGetItemInfoPath = Microsoft.PowerShell.Management\Join-Path $moduleBase.FullName $script:PSGetItemInfoFileName + + # Check if this module got installed using PSGet, read its contents to create a SoftwareIdentity object + if (Microsoft.PowerShell.Management\Test-Path $PSGetItemInfoPath) + { + $psgetItemInfo = DeSerialize-PSObject -Path $PSGetItemInfoPath + + # Add InstalledLocation if this module was installed with older version of PowerShellGet + if(-not (Get-Member -InputObject $psgetItemInfo -Name $script:InstalledLocation)) + { + Microsoft.PowerShell.Utility\Add-Member -InputObject $psgetItemInfo ` + -MemberType NoteProperty ` + -Name $script:InstalledLocation ` + -Value $moduleBase.FullName + } + + $package = New-SoftwareIdentityFromPSGetItemInfo -PSGetItemInfo $psgetItemInfo + + if($package) + { + $script:PSGetInstalledModules["$($psgetItemInfo.Name)$($psgetItemInfo.Version)"] = @{ + SoftwareIdentity = $package + PSGetItemInfo = $psgetItemInfo + } + } + } + } + } +} + +function New-SoftwareIdentityFromPSGetItemInfo +{ + param + ( + [Parameter(Mandatory=$true)] + $PSGetItemInfo + ) + + $SourceLocation = $psgetItemInfo.RepositorySourceLocation + + if(Get-Member -InputObject $PSGetItemInfo -Name $script:PSArtifactType) + { + $artifactType = $psgetItemInfo.Type + } + else + { + $artifactType = $script:PSArtifactTypeModule + } + + $fastPackageReference = New-FastPackageReference -ProviderName (Get-ProviderName -PSCustomObject $psgetItemInfo) ` + -PackageName $psgetItemInfo.Name ` + -Version $psgetItemInfo.Version ` + -Source $SourceLocation ` + -ArtifactType $artifactType + + $links = New-Object -TypeName System.Collections.ArrayList + if($psgetItemInfo.IconUri) + { + $links.Add( (New-Link -Href $psgetItemInfo.IconUri -RelationShip "icon") ) + } + + if($psgetItemInfo.LicenseUri) + { + $links.Add( (New-Link -Href $psgetItemInfo.LicenseUri -RelationShip "license") ) + } + + if($psgetItemInfo.ProjectUri) + { + $links.Add( (New-Link -Href $psgetItemInfo.ProjectUri -RelationShip "project") ) + } + + $entities = New-Object -TypeName System.Collections.ArrayList + if($psgetItemInfo.Author) + { + $entities.Add( (New-Entity -Name $psgetItemInfo.Author -Role 'author') ) + } + + if($psgetItemInfo.CompanyName -and $psgetItemInfo.CompanyName.ToString()) + { + $entities.Add( (New-Entity -Name $psgetItemInfo.CompanyName -Role 'owner') ) + } + + $details = @{ + description = $psgetItemInfo.Description + copyright = $psgetItemInfo.Copyright + published = $psgetItemInfo.PublishedDate.ToString() + installeddate = $null + updateddate = $null + tags = $psgetItemInfo.Tags + releaseNotes = $psgetItemInfo.ReleaseNotes + PackageManagementProvider = (Get-ProviderName -PSCustomObject $psgetItemInfo) + } + + if((Get-Member -InputObject $psgetItemInfo -Name 'InstalledDate') -and $psgetItemInfo.InstalledDate) + { + $details['installeddate'] = $psgetItemInfo.InstalledDate.ToString() + } + + if((Get-Member -InputObject $psgetItemInfo -Name 'UpdatedDate') -and $psgetItemInfo.UpdatedDate) + { + $details['updateddate'] = $psgetItemInfo.UpdatedDate.ToString() + } + + if(Get-Member -InputObject $psgetItemInfo -Name $script:InstalledLocation) + { + $details[$script:InstalledLocation] = $psgetItemInfo.InstalledLocation + } + + $details[$script:PSArtifactType] = $artifactType + + $sourceName = Get-SourceName -Location $SourceLocation + if($sourceName) + { + $details["SourceName"] = $sourceName + } + + $params = @{ + FastPackageReference = $fastPackageReference; + Name = $psgetItemInfo.Name; + Version = $psgetItemInfo.Version; + versionScheme = "MultiPartNumeric"; + Source = $SourceLocation; + Summary = $psgetItemInfo.Description; + Details = $details; + Entities = $entities; + Links = $links + } + + if($sourceName -and $script:PSGetModuleSources[$sourceName].Trusted) + { + $params["FromTrustedSource"] = $true + } + + $sid = New-SoftwareIdentity @params + + return $sid +} + +#endregion + +#region Common functions + +function Get-EnvironmentVariable +{ + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + [parameter(Mandatory = $true)] + [int] + $Target + ) + + if ($Target -eq $script:EnvironmentVariableTarget.Process) + { + return [System.Environment]::GetEnvironmentVariable($Name) + } + elseif ($Target -eq $script:EnvironmentVariableTarget.Machine) + { + $itemPropertyValue = Microsoft.PowerShell.Management\Get-ItemProperty -Path $script:SystemEnvironmentKey -Name $Name -ErrorAction SilentlyContinue + + if($itemPropertyValue) + { + return $itemPropertyValue.$Name + } + } + elseif ($Target -eq $script:EnvironmentVariableTarget.User) + { + $itemPropertyValue = Microsoft.PowerShell.Management\Get-ItemProperty -Path $script:UserEnvironmentKey -Name $Name -ErrorAction SilentlyContinue + + if($itemPropertyValue) + { + return $itemPropertyValue.$Name + } + } +} + +function Set-EnvironmentVariable +{ + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + [parameter()] + [String] + $Value, + + [parameter(Mandatory = $true)] + [int] + $Target + ) + + if ($Target -eq $script:EnvironmentVariableTarget.Process) + { + [System.Environment]::SetEnvironmentVariable($Name, $Value) + + return + } + elseif ($Target -eq $script:EnvironmentVariableTarget.Machine) + { + if ($Name.Length -ge $script:SystemEnvironmentVariableMaximumLength) + { + $message = $LocalizedData.InvalidEnvironmentVariableName -f ($Name, $script:SystemEnvironmentVariableMaximumLength) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'InvalidEnvironmentVariableName' ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Name + return + } + + $Path = $script:SystemEnvironmentKey + } + elseif ($Target -eq $script:EnvironmentVariableTarget.User) + { + if ($Name.Length -ge $script:UserEnvironmentVariableMaximumLength) + { + $message = $LocalizedData.InvalidEnvironmentVariableName -f ($Name, $script:UserEnvironmentVariableMaximumLength) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'InvalidEnvironmentVariableName' ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Name + return + } + + $Path = $script:UserEnvironmentKey + } + + if (!$Value) + { + Microsoft.PowerShell.Management\Remove-ItemProperty $Path -Name $Name -ErrorAction SilentlyContinue + } + else + { + Microsoft.PowerShell.Management\Set-ItemProperty $Path -Name $Name -Value $Value + } +} + +function DeSerialize-PSObject +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + $Path + ) + $filecontent = Microsoft.PowerShell.Management\Get-Content -Path $Path + [System.Management.Automation.PSSerializer]::Deserialize($filecontent) +} + +function Log-ArtifactNotFoundInPSGallery +{ + [CmdletBinding()] + Param + ( + [Parameter()] + [string[]] + $SearchedName, + + [Parameter()] + [string[]] + $FoundName, + + [Parameter(Mandatory=$true)] + [string] + $operationName + ) + + if (-not $script:TelemetryEnabled) + { + return + } + + if(-not $SearchedName) + { + return + } + + $SearchedNameNoWildCards = @() + + # Ignore wild cards + foreach ($artifactName in $SearchedName) + { + if (-not (Test-WildcardPattern $artifactName)) + { + $SearchedNameNoWildCards += $artifactName + } + } + + # Find artifacts searched, but not found in the specified gallery + $notFoundArtifacts = @() + foreach ($element in $SearchedNameNoWildCards) + { + if (-not ($FoundName -contains $element)) + { + $notFoundArtifacts += $element + } + } + + # Perform Telemetry only if searched artifacts are not available in specified Gallery + if ($notFoundArtifacts) + { + [Microsoft.PowerShell.Get.Telemetry]::TraceMessageArtifactsNotFound($notFoundArtifacts, $operationName) + } +} + +# Function to record non-PSGallery registration for telemetry +# Function consumes the type of registration (i.e hosted (http(s)), non-hosted (file/unc)), locations, installation policy, provider and event name +function Log-NonPSGalleryRegistration +{ + [CmdletBinding()] + Param + ( + [Parameter()] + [string] + $sourceLocation, + + [Parameter()] + [string] + $installationPolicy, + + [Parameter()] + [string] + $packageManagementProvider, + + [Parameter()] + [string] + $publishLocation, + + [Parameter()] + [string] + $scriptSourceLocation, + + [Parameter()] + [string] + $scriptPublishLocation, + + [Parameter(Mandatory=$true)] + [string] + $operationName + ) + + if (-not $script:TelemetryEnabled) + { + return + } + + # Initialize source location type - this can be hosted (http(s)) or not hosted (unc/file) + $sourceLocationType = "NON_WEB_HOSTED" + if (Test-WebUri -uri $sourceLocation) + { + $sourceLocationType = "WEB_HOSTED" + } + + # Create a hash of the source location + # We cannot log the actual source location, since this might contain PII (Personally identifiable information) data + $sourceLocationHash = Get-Hash -locationString $sourceLocation + $publishLocationHash = Get-Hash -locationString $publishLocation + $scriptSourceLocationHash = Get-Hash -locationString $scriptSourceLocation + $scriptPublishLocationHash = Get-Hash -locationString $scriptPublishLocation + + # Log the telemetry event + [Microsoft.PowerShell.Get.Telemetry]::TraceMessageNonPSGalleryRegistration($sourceLocationType, $sourceLocationHash, $installationPolicy, $packageManagementProvider, $publishLocationHash, $scriptSourceLocationHash, $scriptPublishLocationHash, $operationName) +} + +# Returns a SHA1 hash of the specified string +function Get-Hash +{ + [CmdletBinding()] + Param + ( + [string] + $locationString + ) + + if(-not $locationString) + { + return "" + } + + $sha1Object = New-Object System.Security.Cryptography.SHA1Managed + $stringHash = $sha1Object.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($locationString)); + $stringHashInHex = [System.BitConverter]::ToString($stringHash) + + if ($stringHashInHex) + { + # Remove all dashes in the hex string + return $stringHashInHex.Replace('-', '') + } + + return "" +} + +function Get-ValidModuleLocation +{ + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $LocationString, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $ParameterName + ) + + # Get the actual Uri from the Location + if(-not (Microsoft.PowerShell.Management\Test-Path $LocationString)) + { + # Append '/api/v2/' to the $LocationString, return if that URI works. + if(($LocationString -notmatch 'LinkID') -and + -not ($LocationString.EndsWith('/api/v2', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('/api/v2/', [System.StringComparison]::OrdinalIgnoreCase)) + ) + { + $tempLocation = $null + + if($LocationString.EndsWith('/', [System.StringComparison]::OrdinalIgnoreCase)) + { + $tempLocation = $LocationString + 'api/v2/' + } + else + { + $tempLocation = $LocationString + '/api/v2/' + } + + if($tempLocation) + { + # Ping and resolve the specified location + $tempLocation = Resolve-Location -Location $tempLocation ` + -LocationParameterName $ParameterName ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + if($tempLocation) + { + return $tempLocation + } + # No error if we can't resolve the URL appended with '/api/v2/' + } + } + + # Ping and resolve the specified location + $LocationString = Resolve-Location -Location $LocationString ` + -LocationParameterName $ParameterName ` + -CallerPSCmdlet $PSCmdlet + } + + return $LocationString +} + +function Save-ModuleSources +{ + if($script:PSGetModuleSources) + { + if(-not (Microsoft.PowerShell.Management\Test-Path $script:PSGetAppLocalPath)) + { + $null = Microsoft.PowerShell.Management\New-Item -Path $script:PSGetAppLocalPath ` + -ItemType Directory -Force ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false -WhatIf:$false + } + Microsoft.PowerShell.Utility\Out-File -FilePath $script:PSGetModuleSourcesFilePath -Force -InputObject ([System.Management.Automation.PSSerializer]::Serialize($script:PSGetModuleSources)) + } +} + +function Test-ModuleSxSVersionSupport +{ + # Side-by-Side module version is avialable on PowerShell 5.0 or later versions only + # By default, PowerShell module versions will be installed/updated Side-by-Side. + $PSVersionTable.PSVersion -ge [Version]"5.0" +} + +function Test-ModuleInstalled +{ + [CmdletBinding(PositionalBinding=$false)] + [OutputType("PSModuleInfo")] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Name, + + [Parameter()] + [Version] + $RequiredVersion + ) + + # Check if module is already installed + $availableModule = Microsoft.PowerShell.Core\Get-Module -ListAvailable -Name $Name -Verbose:$false | + Microsoft.PowerShell.Core\Where-Object {-not (Test-ModuleSxSVersionSupport) -or -not $RequiredVersion -or ($RequiredVersion -eq $_.Version)} | + Microsoft.PowerShell.Utility\Select-Object -Unique + + return $availableModule +} + +function Test-ScriptInstalled +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Name, + + [Parameter()] + [Version] + $RequiredVersion + ) + + $scriptInfo = $null + $scriptFileName = "$Name.ps1" + $scriptPaths = @($script:ProgramFilesScriptsPath, $script:MyDocumentsScriptsPath) + $scriptInfos = @() + + foreach ($location in $scriptPaths) + { + $scriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $location -ChildPath $scriptFileName + + if(Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf) + { + $scriptInfo = $null + try + { + $scriptInfo = Test-ScriptFileInfo -Path $scriptFilePath -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + catch + { + # Ignore any terminating error from the Test-ScriptFileInfo cmdlet, + # if it does not contain valid Script metadata + Write-Verbose -Message "$_" + } + + if($scriptInfo) + { + $scriptInfos += $scriptInfo + } + else + { + # Since the script file doesn't contain the valid script metadata, + # create dummy PSScriptInfo object with 0.0 version + $scriptInfo = New-PSScriptInfoObject -Path $scriptFilePath + $scriptInfo.$script:Version = [Version]'0.0' + + $scriptInfos += $scriptInfo + } + } + } + + $scriptInfo = $scriptInfos | Microsoft.PowerShell.Core\Where-Object { + (-not $RequiredVersion) -or ($RequiredVersion -eq $_.Version) + } | Microsoft.PowerShell.Utility\Select-Object -First 1 + + return $scriptInfo +} + +function New-PSScriptInfoObject +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [string] + $Path + ) + + $PSScriptInfo = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{}) + $script:PSScriptInfoProperties | Microsoft.PowerShell.Core\ForEach-Object { + Microsoft.PowerShell.Utility\Add-Member -InputObject $PSScriptInfo ` + -MemberType NoteProperty ` + -Name $_ ` + -Value $null + } + + $PSScriptInfo.$script:Name = [System.IO.Path]::GetFileNameWithoutExtension($Path) + $PSScriptInfo.$script:Path = $Path + $PSScriptInfo.$script:ScriptBase = (Microsoft.PowerShell.Management\Split-Path -Path $Path -Parent) + + return $PSScriptInfo +} + +function Get-OrderedPSScriptInfoObject +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [PSCustomObject] + $PSScriptInfo + ) + + $NewPSScriptInfo = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + $script:Name = $PSScriptInfo.$script:Name + $script:Version = $PSScriptInfo.$script:Version + $script:Guid = $PSScriptInfo.$script:Guid + $script:Path = $PSScriptInfo.$script:Path + $script:ScriptBase = $PSScriptInfo.$script:ScriptBase + $script:Description = $PSScriptInfo.$script:Description + $script:Author = $PSScriptInfo.$script:Author + $script:CompanyName = $PSScriptInfo.$script:CompanyName + $script:Copyright = $PSScriptInfo.$script:Copyright + $script:Tags = $PSScriptInfo.$script:Tags + $script:ReleaseNotes = $PSScriptInfo.$script:ReleaseNotes + $script:RequiredModules = $PSScriptInfo.$script:RequiredModules + $script:ExternalModuleDependencies = $PSScriptInfo.$script:ExternalModuleDependencies + $script:RequiredScripts = $PSScriptInfo.$script:RequiredScripts + $script:ExternalScriptDependencies = $PSScriptInfo.$script:ExternalScriptDependencies + $script:LicenseUri = $PSScriptInfo.$script:LicenseUri + $script:ProjectUri = $PSScriptInfo.$script:ProjectUri + $script:IconUri = $PSScriptInfo.$script:IconUri + $script:DefinedCommands = $PSScriptInfo.$script:DefinedCommands + $script:DefinedFunctions = $PSScriptInfo.$script:DefinedFunctions + $script:DefinedWorkflows = $PSScriptInfo.$script:DefinedWorkflows + }) + + $NewPSScriptInfo.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.PSScriptInfo") + + return $NewPSScriptInfo +} + +function Get-AvailableScriptFilePath +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter()] + [string] + $Name + ) + + $scriptInfo = $null + $scriptFileName = '*.ps1' + $scriptBasePaths = @($script:ProgramFilesScriptsPath, $script:MyDocumentsScriptsPath) + $scriptFilePaths = @() + $wildcardPattern = $null + + if($Name) + { + if(Test-WildcardPattern -Name $Name) + { + $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $Name,$script:wildcardOptions + } + else + { + $scriptFileName = "$Name.ps1" + } + + } + + foreach ($location in $scriptBasePaths) + { + $scriptFiles = Get-ChildItem -Path $location ` + -Filter $scriptFileName ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + + if($wildcardPattern) + { + $scriptFiles | Microsoft.PowerShell.Core\ForEach-Object { + if($wildcardPattern.IsMatch($_.BaseName)) + { + $scriptFilePaths += $_.FullName + } + } + } + else + { + $scriptFiles | Microsoft.PowerShell.Core\ForEach-Object { $scriptFilePaths += $_.FullName } + } + } + + return $scriptFilePaths +} + +function Get-InstalledScriptFilePath +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter()] + [string] + $Name + ) + + $installedScriptFilePaths = @() + $scriptFilePaths = Get-AvailableScriptFilePath @PSBoundParameters + + foreach ($scriptFilePath in $scriptFilePaths) + { + $scriptInfo = Test-ScriptInstalled -Name ([System.IO.Path]::GetFileNameWithoutExtension($scriptFilePath)) + + if($scriptInfo) + { + $installedScriptInfoFilePath = $null + $installedScriptInfoFileName = "$($scriptInfo.Name)_$script:InstalledScriptInfoFileName" + + if($scriptInfo.Path.StartsWith($script:ProgramFilesScriptsPath, [System.StringComparison]::OrdinalIgnoreCase)) + { + $installedScriptInfoFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:ProgramFilesInstalledScriptInfosPath ` + -ChildPath $installedScriptInfoFileName + } + elseif($scriptInfo.Path.StartsWith($script:MyDocumentsScriptsPath, [System.StringComparison]::OrdinalIgnoreCase)) + { + $installedScriptInfoFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsInstalledScriptInfosPath ` + -ChildPath $installedScriptInfoFileName + } + + if($installedScriptInfoFilePath -and (Microsoft.PowerShell.Management\Test-Path -Path $installedScriptInfoFilePath -PathType Leaf)) + { + $installedScriptFilePaths += $scriptInfo.Path + } + } + } + + return $installedScriptFilePaths +} + + +function Update-ModuleManifest +{ +<# +.ExternalHelp PSGet.psm1-help.xml +#> +[CmdletBinding(SupportsShouldProcess=$true, + PositionalBinding=$false, + HelpUri='http://go.microsoft.com/fwlink/?LinkId=619311')] + Param + ( + [Parameter(Mandatory=$true, + Position=0, + ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Path, + + [ValidateNotNullOrEmpty()] + [Object[]] + $NestedModules, + + [ValidateNotNullOrEmpty()] + [Guid] + $Guid, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Author, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String] + $CompanyName, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Copyright, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $RootModule, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Version] + $ModuleVersion, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $Description, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.Reflection.ProcessorArchitecture] + $ProcessorArchitecture, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Version] + $PowerShellVersion, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Version] + $ClrVersion, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Version] + $DotNetFrameworkVersion, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String] + $PowerShellHostName, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Version] + $PowerShellHostVersion, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Object[]] + $RequiredModules, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $TypesToProcess, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $FormatsToProcess, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $ScriptsToProcess, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $RequiredAssemblies, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $FileList, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [object[]] + $ModuleList, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $FunctionsToExport, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $AliasesToExport, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $VariablesToExport, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $CmdletsToExport, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $DscResourcesToExport, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.Collections.Hashtable] + $PrivateData, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string[]] + $Tags, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $ProjectUri, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $LicenseUri, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $IconUri, + + [Parameter()] + [string[]] + $ReleaseNotes, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Uri] + $HelpInfoUri, + + [Parameter()] + [switch] + $PassThru, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String] + $DefaultCommandPrefix, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String[]] + $ExternalModuleDependencies, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String[]] + $PackageManagementProviders + ) + + + Import-LocalizedData -BindingVariable ModuleManifestHashTable ` + -FileName (Microsoft.PowerShell.Management\Split-Path $Path -Leaf) ` + -BaseDirectory (Microsoft.PowerShell.Management\Split-Path $Path -Parent) ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + + if(-not (Microsoft.PowerShell.Management\Test-Path $Path)) + { + $message = $LocalizedData.UpdateModuleManifestPathCannotFound -f ($Path) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidModuleManifestFilePath" ` + -ExceptionObject $Path ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + + #Get the original module manifest and migrate all the fields to the new module manifest, including the specified parameter values + try + { + $moduleInfo = Microsoft.PowerShell.Core\Test-ModuleManifest -Path $Path -ErrorAction Stop + } + catch + { + $message = $LocalizedData.TestModuleManifestFail -f ($_.Exception.Message) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidModuleManifestFile" ` + -ExceptionObject $Path ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } + + #Params to pass to New-ModuleManifest module + $params = @{} + + #NestedModules is read-only property + if($NestedModules) + { + $params.Add("NestedModules",$NestedModules) + } + elseif($moduleInfo.NestedModules) + { + #Get the original module info from ManifestHashTab + if($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("NestedModules")) + { + $params.Add("NestedModules",$ModuleManifestHashtable.NestedModules) + } + } + + #Guid is read-only property + if($Guid) + { + $params.Add("Guid",$Guid) + } + elseif($moduleInfo.Guid) + { + $params.Add("Guid",$moduleInfo.Guid) + } + + if($Author) + { + $params.Add("Author",$Author) + } + elseif($moduleInfo.Author) + { + $params.Add("Author",$moduleInfo.Author) + } + + if($CompanyName) + { + $params.Add("CompanyName",$CompanyName) + } + elseif($moduleInfo.CompanyName) + { + $params.Add("CompanyName",$moduleInfo.CompanyName) + } + + if($Copyright) + { + $params.Add("CopyRight",$Copyright) + } + elseif($moduleInfo.Copyright) + { + $params.Add("Copyright",$moduleInfo.Copyright) + } + + if($RootModule) + { + $params.Add("RootModule",$RootModule) + } + elseif($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("RootModule") -and $moduleInfo.RootModule) + { + $params.Add("RootModule",$ModuleManifestHashTable.RootModule) + } + + if($ModuleVersion) + { + $params.Add("ModuleVersion",$ModuleVersion) + } + elseif($moduleInfo.Version) + { + $params.Add("ModuleVersion",$moduleInfo.Version) + } + + if($Description) + { + $params.Add("Description",$Description) + } + elseif($moduleInfo.Description) + { + $params.Add("Description",$moduleInfo.Description) + } + + if($ProcessorArchitecture) + { + $params.Add("ProcessorArchitecture",$ProcessorArchitecture) + } + #Check if ProcessorArchitecture has a value and is not 'None' on lower verison PS + elseif($moduleInfo.ProcessorArchitecture -and $moduleInfo.ProcessorArchitecture -ne 'None') + { + $params.Add("ProcessorArchitecture",$moduleInfo.ProcessorArchitecture) + } + + if($PowerShellVersion) + { + $params.Add("PowerShellVersion",$PowerShellVersion) + } + elseif($moduleinfo.PowerShellVersion) + { + $params.Add("PowerShellVersion",$moduleinfo.PowerShellVersion) + } + + if($ClrVersion) + { + $params.Add("ClrVersion",$ClrVersion) + } + elseif($moduleInfo.ClrVersion) + { + $params.Add("ClrVersion",$moduleInfo.ClrVersion) + } + + if($DotNetFrameworkVersion) + { + $params.Add("DotNetFrameworkVersion",$DotNetFrameworkVersion) + } + elseif($moduleInfo.DotNetFrameworkVersion) + { + $params.Add("DotNetFrameworkVersion",$moduleInfo.DotNetFrameworkVersion) + } + + if($PowerShellHostName) + { + $params.Add("PowerShellHostName",$PowerShellHostName) + } + elseif($moduleInfo.PowerShellHostName) + { + $params.Add("PowerShellHostName",$moduleInfo.PowerShellHostName) + } + + if($PowerShellHostVersion) + { + $params.Add("PowerShellHostVersion",$PowerShellHostVersion) + } + elseif($moduleInfo.PowerShellHostVersion) + { + $params.Add("PowerShellHostVersion",$moduleInfo.PowerShellHostVersion) + } + + if($RequiredModules) + { + $params.Add("RequiredModules",$RequiredModules) + } + elseif($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("RequiredModules") -and $moduleInfo.RequiredModules) + { + $params.Add("RequiredModules",$ModuleManifestHashtable.RequiredModules) + } + + if($TypesToProcess) + { + $params.Add("TypesToProcess",$TypesToProcess) + } + elseif($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("TypesToProcess") -and $moduleInfo.ExportedTypeFiles) + { + $params.Add("TypesToProcess",$ModuleManifestHashTable.TypesToProcess) + } + + if($FormatsToProcess) + { + $params.Add("FormatsToProcess",$FormatsToProcess) + } + elseif($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("FormatsToProcess") -and $moduleInfo.ExportedFormatFiles) + { + $params.Add("FormatsToProcess",$ModuleManifestHashTable.FormatsToProcess) + } + + if($ScriptsToProcess) + { + $params.Add("ScriptsToProcess",$ScriptstoProcess) + } + elseif($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("ScriptsToProcess") -and $moduleInfo.Scripts) + { + $params.Add("ScriptsToProcess",$ModuleManifestHashTable.ScriptsToProcess) + } + + if($RequiredAssemblies) + { + $params.Add("RequiredAssemblies",$RequiredAssemblies) + } + elseif($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("RequiredAssemblies") -and $moduleInfo.RequiredAssemblies) + { + $params.Add("RequiredAssemblies",$moduleInfo.RequiredAssemblies) + } + + if($FileList) + { + $params.Add("FileList",$FileList) + } + elseif($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("FileList") -and $moduleInfo.FileList) + { + $params.Add("FileList",$ModuleManifestHashTable.FileList) + } + + #Make sure every path defined under FileList is within module base + $moduleBase = $moduleInfo.ModuleBase + foreach($file in $params["FileList"]) + { + #If path is not root path, append the module base to it and check if the file exists + if(-not [System.IO.Path]::IsPathRooted($file)) + { + $combinedPath = Join-Path $moduleBase -ChildPath $file + } + else + { + $combinedPath = $file + } + if(-not (Microsoft.PowerShell.Management\Test-Path -Type Leaf -LiteralPath $combinedPath)) + { + $message = $LocalizedData.FilePathInFileListNotWithinModuleBase -f ($file,$moduleBase) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "FilePathInFileListNotWithinModuleBase" ` + -ExceptionObject $file ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + + return + } + } + + if($ModuleList) + { + $params.Add("ModuleList",$ModuleList) + } + elseif($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("ModuleList") -and $moduleInfo.ModuleList) + { + $params.Add("ModuleList",$ModuleManifestHashtable.ModuleList) + } + + if($FunctionsToExport) + { + $params.Add("FunctionsToExport",$FunctionsToExport) + } + + elseif($moduleInfo.ExportedFunctions) + { + #Since $moduleInfo.ExportedFunctions is a hashtable, we need to take the name of the + #functions and make them into a list + $params.Add("FunctionsToExport",($moduleInfo.ExportedFunctions.Keys -split ' ')) + } + + + if($AliasesToExport) + { + $params.Add("AliasesToExport",$AliasesToExport) + } + elseif($moduleInfo.ExportedAliases) + { + $params.Add("AliasesToExport",($moduleInfo.ExportedAliases.Keys -split ' ')) + } + if($VariablesToExport) + { + $params.Add("VariablesToExport",$VariablesToExport) + } + elseif($moduleInfo.ExportedVariables) + { + $params.Add("VariablesToExport",($moduleInfo.ExportedVariables.Keys -split ' ')) + } + if($CmdletsToExport) + { + $params.Add("CmdletsToExport", $CmdletsToExport) + } + elseif($moduleInfo.ExportedCmdlets) + { + $params.Add("CmdletsToExport",($moduleInfo.ExportedCmdlets.Keys -split ' ')) + } + if($DscResourcesToExport) + { + #DscResourcesToExport field is not available in PowerShell version lower than 5.0 + + if (($PSVersionTable.PSVersion -lt [Version]"5.0") -or ($PowerShellVersion -and $PowerShellVersion -lt [Version]"5.0") ` + -or (-not $PowerShellVersion -and $moduleInfo.PowerShellVersion -and $moduleInfo.PowerShellVersion -lt [Version]"5.0") ` + -or (-not $PowerShellVersion -and -not $moduleInfo.PowerShellVersion)) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $LocalizedData.ExportedDscResourcesNotSupportedOnLowerPowerShellVersion ` + -ErrorId "ExportedDscResourcesNotSupported" ` + -ExceptionObject $DscResourcesToExport ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } + + $params.Add("DscResourcesToExport",$DscResourcesToExport) + } + elseif(Microsoft.PowerShell.Utility\Get-Member -InputObject $moduleInfo -name "ExportedDscResources") + { + if($moduleInfo.ExportedDscResources) + { + $params.Add("DscResourcesToExport",$moduleInfo.ExportedDscResources) + } + } + + if($HelpInfoUri) + { + $params.Add("HelpInfoUri",$HelpInfoUri) + } + elseif($moduleInfo.HelpInfoUri) + { + $params.Add("HelpInfoUri",$moduleInfo.HelpInfoUri) + } + + if($DefaultCommandPrefix) + { + $params.Add("DefaultCommandPrefix",$DefaultCommandPrefix) + } + elseif($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey("DefaultCommandPrefix") -and $ModuleManifestHashTable.DefaultCommandPrefix) + { + $params.Add("DefaultCommandPrefix",$ModuleManifestHashTable.DefaultCommandPrefix) + } + + #Create a temp file within the directory and generate a new temporary manifest with the input + $tempPath = Microsoft.PowerShell.Management\Join-Path -Path $moduleInfo.ModuleBase -ChildPath "PSGet_$($moduleInfo.Name).psd1" + $params.Add("Path",$tempPath) + + try + { + #Terminates if there is error creating new module manifest + try{ + Microsoft.PowerShell.Core\New-ModuleManifest @params -Confirm:$false -WhatIf:$false + } + catch + { + $ErrorMessage = $LocalizedData.UpdatedModuleManifestNotValid -f ($Path, $_.Exception.Message) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $ErrorMessage ` + -ErrorId "NewModuleManifestFailure" ` + -ExceptionObject $params ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } + + #Manually update the section in PrivateData since New-ModuleManifest works differently on different PS version + $PrivateDataInput = "" + $ExistingData = $moduleInfo.PrivateData + $Data = @{} + if($ExistingData) + { + foreach($key in $ExistingData.Keys) + { + if($key -ne "PSData"){ + $Data.Add($key,$ExistingData[$key]) + } + else + { + $PSData = $ExistingData["PSData"] + foreach($entry in $PSData.Keys) + { + $Data.Add($entry,$PSData[$Entry]) + } + } + } + } + + if($PrivateData) + { + foreach($key in $PrivateData.Keys) + { + #if user provides PSData within PrivateData, we will parse through the PSData + if($key -ne "PSData") + { + $Data[$key] = $PrivateData[$Key] + } + + else + { + $PSData = $ExistingData["PSData"] + foreach($entry in $PSData.Keys) + { + $Data[$entry] = $PSData[$entry] + } + } + } + } + + #Tags is a read-only property + if($Tags) + { + $Data["Tags"] = $Tags + } + + + #The following Uris and ReleaseNotes cannot be empty + if($ProjectUri) + { + $Data["ProjectUri"] = $ProjectUri + } + + if($LicenseUri) + { + $Data["LicenseUri"] = $LicenseUri + } + if($IconUri) + { + $Data["IconUri"] = $IconUri + } + + if($ReleaseNotes) + { + #If value is provided as an array, we append the string. + $Data["ReleaseNotes"] = $($ReleaseNotes -join "`r`n") + } + + if($ExternalModuleDependencies) + { + #ExternalModuleDependencies have to be specified either under $RequiredModules or $NestedModules + #Extract all the module names specified in the moduleInfo of NestedModules and RequiredModules + $DependentModuleNames = @() + foreach($moduleInfo in $params["NestedModules"]) + { + if($moduleInfo.GetType() -eq [System.Collections.Hashtable]) + { + $DependentModuleNames += $moduleInfo.ModuleName + } + } + + foreach($moduleInfo in $params["RequiredModules"]) + { + if($moduleInfo.GetType() -eq [System.Collections.Hashtable]) + { + $DependentModuleNames += $moduleInfo.ModuleName + } + } + + foreach($dependency in $ExternalModuleDependencies) + { + if($params["NestedModules"] -notcontains $dependency -and + $params["RequiredModules"] -notContains $dependency -and + $DependentModuleNames -notcontains $dependency) + { + $message = $LocalizedData.ExternalModuleDependenciesNotSpecifiedInRequiredOrNestedModules -f ($dependency) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidExternalModuleDependencies" ` + -ExceptionObject $Exception ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } + } + if($Data.ContainsKey("ExternalModuleDependencies")) + { + $Data["ExternalModuleDependencies"] = $ExternalModuleDependencies + } + else + { + $Data.Add("ExternalModuleDependencies", $ExternalModuleDependencies) + } + } + if($PackageManagementProviders) + { + #Check if the provided value is within the relative path + $ModuleBase = Microsoft.PowerShell.Management\Split-Path $Path -Parent + $Files = Microsoft.PowerShell.Management\Get-ChildItem -Path $ModuleBase + foreach($provider in $PackageManagementProviders) + { + if ($Files.Name -notcontains $provider) + { + $message = $LocalizedData.PackageManagementProvidersNotInModuleBaseFolder -f ($provider,$ModuleBase) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidPackageManagementProviders" ` + -ExceptionObject $PackageManagementProviders ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } + } + + $Data["PackageManagementProviders"] = $PackageManagementProviders + } + $PrivateDataInput = Get-PrivateData -PrivateData $Data + + #Repleace the PrivateData section by first locating the linenumbers of start line and endline. + $PrivateDataBegin = Select-String -Path $tempPath -Pattern "PrivateData =" + $PrivateDataBeginLine = $PrivateDataBegin.LineNumber + + $newManifest = Microsoft.PowerShell.Management\Get-Content -Path $tempPath + #Look up the endline of PrivateData section by finding the matching brackets since private data could + #consist of multiple pairs of brackets. + $PrivateDataEndLine=0 + if($PrivateDataBegin -match "@{") + { + $leftBrace = 0 + $EndLineOfFile = $newManifest.Length-1 + + For($i = $PrivateDataBeginLine;$i -lt $EndLineOfFile; $i++) + { + if($newManifest[$i] -match "{") + { + $leftBrace ++ + } + elseif($newManifest[$i] -match "}") + { + if($leftBrace -gt 0) + { + $leftBrace -- + } + else + { + $PrivateDataEndLine = $i + break + } + } + } + } + + + try + { + if($PrivateDataEndLine -ne 0) + { + #If PrivateData section has more than one line, we will remove the old content and insert the new PrivataData + $newManifest | where {$_.readcount -le $PrivateDataBeginLine -or $_.readcount -gt $PrivateDataEndLine+1} ` + | ForEach-Object { + $_ + if($_ -match "PrivateData = ") + { + $PrivateDataInput + } + } | Set-Content -Path $tempPath -Confirm:$false -WhatIf:$false + } + + #In lower version, PrivateData is just a single line + else + { + $PrivateDataForDownlevelPS = "PrivateData = @{ `n"+$PrivateDataInput + + $newManifest | where {$_.readcount -le $PrivateDataBeginLine -or $_.readcount -gt $PrivateDataBeginLine } ` + | ForEach-Object { + $_ + if($_ -match "PrivateData = ") + { + $PrivateDataForDownlevelPS + } + } | Set-Content -Path $tempPath -Confirm:$false -WhatIf:$false + } + + #Verify the new module manifest is valid + $testModuleInfo = Microsoft.PowerShell.Core\Test-ModuleManifest -Path $tempPath ` + -Verbose:$VerbosePreference ` + } + #Catch the exceptions from Test-ModuleManifest + catch + { + $message = $LocalizedData.UpdatedModuleManifestNotValid -f ($Path, $_.Exception.Message) + + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "UpdateManifestFileFail" ` + -ExceptionObject $_.Exception ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } + + + $newContent = Microsoft.PowerShell.Management\Get-Content -Path $tempPath + + try{ + #Ask for confirmation of the new manifest before replacing the original one + if($PSCmdlet.ShouldProcess($Path,$LocalizedData.UpdateManifestContentMessage+$newContent)) + { + Microsoft.PowerShell.Management\Set-Content -Path $Path -Value $newContent -Confirm:$false -WhatIf:$false + } + + #Return the new content if -PassThru is specified + if($PassThru) + { + return $newContent + } + } + catch + { + $message = $LocalizedData.ManifestFileReadWritePermissionDenied -f ($Path) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "ManifestFileReadWritePermissionDenied" ` + -ExceptionObject $Path ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + } + } + finally + { + Microsoft.PowerShell.Management\Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } +} + +#Utility function to help form the content string for PrivateData +function Get-PrivateData +{ + param + ( + [System.Collections.Hashtable] + $PrivateData + ) + + if($PrivateData.Keys.Count -eq 0) + { + $content = " + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # External dependent modules of this module + # ExternalModuleDependencies = '' + + } # End of PSData hashtable + +} # End of PrivateData hashtable" + return $content + } + + + #Validate each of the property of PSData is of the desired data type + $Tags= $PrivateData["Tags"] -join "','" | %{"'$_'"} + $LicenseUri = $PrivateData["LicenseUri"]| %{"'$_'"} + $ProjectUri = $PrivateData["ProjectUri"] | %{"'$_'"} + $IconUri = $PrivateData["IconUri"] | %{"'$_'"} + $ReleaseNotesEscape = $PrivateData["ReleaseNotes"] -Replace "'","''" + $ReleaseNotes = $ReleaseNotesEscape | %{"'$_'"} + $ExternalModuleDependencies = $PrivateData["ExternalModuleDependencies"] -join "','" | %{"'$_'"} + + $DefaultProperties = @("Tags","LicenseUri","ProjectUri","IconUri","ReleaseNotes","ExternalModuleDependencies") + + $ExtraProperties = @() + foreach($key in $PrivateData.Keys) + { + if($DefaultProperties -notcontains $key) + { + $PropertyString = "#"+"$key"+ " of this module" + $PropertyString += "`r`n " + $PropertyString += $key +" = " + "'"+$PrivateData[$key]+"'" + $ExtraProperties += ,$PropertyString + } + } + + $ExtraPropertiesString = "" + $firstProperty = $true + foreach($property in $ExtraProperties) + { + if($firstProperty) + { + $firstProperty = $false + } + else + { + $ExtraPropertiesString += "`r`n`r`n " + } + $ExtraPropertiesString += $Property + } + + $TagsLine ="# Tags = @()" + if($Tags -ne "''") + { + $TagsLine = "Tags = "+$Tags + } + $LicenseUriLine = "# LicenseUri = ''" + if($LicenseUri -ne "''") + { + $LicenseUriLine = "LicenseUri = "+$LicenseUri + } + $ProjectUriLine = "# ProjectUri = ''" + if($ProjectUri -ne "''") + { + $ProjectUriLine = "ProjectUri = " +$ProjectUri + } + $IconUriLine = "# IconUri = ''" + if($IconUri -ne "''") + { + $IconUriLine = "IconUri = " +$IconUri + } + $ReleaseNotesLine = "# ReleaseNotes = ''" + if($ReleaseNotes -ne "''") + { + $ReleaseNotesLine = "ReleaseNotes = "+$ReleaseNotes + } + $ExternalModuleDependenciesLine ="# ExternalModuleDependencies = ''" + if($ExternalModuleDependencies -ne "''") + { + $ExternalModuleDependenciesLine = "ExternalModuleDependencies = "+$ExternalModuleDependencies + } + + if(-not $ExtraPropertiesString -eq "") + { + $Content = " + ExtraProperties + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + $TagsLine + + # A URL to the license for this module. + $LicenseUriLine + + # A URL to the main website for this project. + $ProjectUriLine + + # A URL to an icon representing this module. + $IconUriLine + + # ReleaseNotes of this module + $ReleaseNotesLine + + # External dependent modules of this module + $ExternalModuleDependenciesLine + + } # End of PSData hashtable + +} # End of PrivateData hashtable" + + #Replace the Extra PrivateData in the block + $Content -replace "ExtraProperties", $ExtraPropertiesString + } + else + { + $content = " + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + $TagsLine + + # A URL to the license for this module. + $LicenseUriLine + + # A URL to the main website for this project. + $ProjectUriLine + + # A URL to an icon representing this module. + $IconUriLine + + # ReleaseNotes of this module + $ReleaseNotesLine + + # External dependent modules of this module + $ExternalModuleDependenciesLine + + } # End of PSData hashtable + + } # End of PrivateData hashtable" + return $content + } +} + +function Copy-ScriptFile +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $SourcePath, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $DestinationPath, + + [Parameter(Mandatory=$true)] + [ValidateNotNull()] + [PSCustomObject] + $PSGetItemInfo, + + [Parameter()] + [string] + $Scope + ) + + # Copy the script file to destination + if(-not (Microsoft.PowerShell.Management\Test-Path -Path $DestinationPath)) + { + $null = Microsoft.PowerShell.Management\New-Item -Path $DestinationPath ` + -ItemType Directory ` + -Force ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue ` + -Confirm:$false ` + -WhatIf:$false + } + + Microsoft.PowerShell.Management\Copy-Item -Path $SourcePath -Destination $DestinationPath -Force -Confirm:$false -WhatIf:$false -Verbose + + if($Scope) + { + # Create _InstalledScriptInfo.xml + $InstalledScriptInfoFileName = "$($PSGetItemInfo.Name)_$script:InstalledScriptInfoFileName" + + if($scope -eq 'AllUsers') + { + $scriptInfopath = Microsoft.PowerShell.Management\Join-Path -Path $script:ProgramFilesInstalledScriptInfosPath ` + -ChildPath $InstalledScriptInfoFileName + } + else + { + $scriptInfopath = Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsInstalledScriptInfosPath ` + -ChildPath $InstalledScriptInfoFileName + } + + Microsoft.PowerShell.Utility\Out-File -FilePath $scriptInfopath ` + -Force ` + -InputObject ([System.Management.Automation.PSSerializer]::Serialize($PSGetItemInfo)) + } +} + +function Copy-Module +{ + [CmdletBinding(PositionalBinding=$false)] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $SourcePath, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $DestinationPath, + + [Parameter(Mandatory=$true)] + [ValidateNotNull()] + [PSCustomObject] + $PSGetItemInfo + ) + + if(Microsoft.PowerShell.Management\Test-Path $DestinationPath) + { + Microsoft.PowerShell.Management\Remove-Item -Path $DestinationPath -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } + + # Copy the module to destination + $null = Microsoft.PowerShell.Management\New-Item -Path $DestinationPath -ItemType Directory -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + Microsoft.PowerShell.Management\Copy-Item -Path "$SourcePath\*" -Destination $DestinationPath -Force -Recurse -Confirm:$false -WhatIf:$false + + # Remove the *.nupkg file + if(Microsoft.PowerShell.Management\Test-Path "$DestinationPath\$($PSGetItemInfo.Name).nupkg") + { + Microsoft.PowerShell.Management\Remove-Item -Path "$DestinationPath\$($PSGetItemInfo.Name).nupkg" -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false + } + + # Create PSGetModuleInfo.xml + $psgetItemInfopath = Microsoft.PowerShell.Management\Join-Path $DestinationPath $script:PSGetItemInfoFileName + + Microsoft.PowerShell.Utility\Out-File -FilePath $psgetItemInfopath -Force -InputObject ([System.Management.Automation.PSSerializer]::Serialize($PSGetItemInfo)) + + [System.IO.File]::SetAttributes($psgetItemInfopath, [System.IO.FileAttributes]::Hidden) +} + +function Test-FileInUse +{ + [CmdletBinding()] + [OutputType([bool])] + param + ( + [string] + $FilePath + ) + + if(Microsoft.PowerShell.Management\Test-Path -LiteralPath $FilePath -PathType Leaf) + { + # Attempts to open a file and handles the exception if the file is already open/locked + try + { + $fileInfo = New-Object System.IO.FileInfo $FilePath + $fileStream = $fileInfo.Open( [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None ) + + if ($fileStream) + { + $fileStream.Close() + } + } + catch + { + Write-Debug "In Test-FileInUse function, unable to open the $FilePath file in ReadWrite access. $_" + return $true + } + } + + return $false +} + +function Test-ModuleInUse +{ + [CmdletBinding()] + [OutputType([bool])] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $ModuleBasePath, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $ModuleName, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [Version] + $ModuleVersion + ) + + $FileList = Get-ChildItem -Path $ModuleBasePath ` + -File ` + -Recurse ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + $IsModuleInUse = $false + + foreach($file in $FileList) + { + $IsModuleInUse = Test-FileInUse -FilePath $file.FullName + + if($IsModuleInUse) + { + break + } + } + + if($IsModuleInUse) + { + $message = $LocalizedData.ModuleVersionInUse -f ($ModuleVersion, $ModuleName) + Write-Error -Message $message -ErrorId 'ModuleIsInUse' -Category InvalidOperation + + return $true + } + + return $false +} + +function Test-ValidManifestModule +{ + [CmdletBinding()] + [OutputType([bool])] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $ModuleBasePath + ) + + $moduleName = Microsoft.PowerShell.Management\Split-Path $ModuleBasePath -Leaf + $manifestPath = Microsoft.PowerShell.Management\Join-Path $ModuleBasePath "$moduleName.psd1" + $PSModuleInfo = $null + + if(Microsoft.PowerShell.Management\Test-Path $manifestPath) + { + $PSModuleInfo = Microsoft.PowerShell.Core\Test-ModuleManifest -Path $manifestPath -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + + return $PSModuleInfo +} + +function Get-ScriptSourceLocation +{ + [CmdletBinding()] + Param + ( + [Parameter()] + [String] + $Location + ) + + $scriptLocation = $null + + if($Location) + { + # For local dir or SMB-share locations, ScriptSourceLocation is SourceLocation. + if(Microsoft.PowerShell.Management\Test-Path -Path $Location) + { + $scriptLocation = $Location + } + else + { + $tempScriptLocation = $null + + if($Location.EndsWith('/api/v2', [System.StringComparison]::OrdinalIgnoreCase)) + { + $tempScriptLocation = $Location + '/items/psscript/' + } + elseif($Location.EndsWith('/api/v2/', [System.StringComparison]::OrdinalIgnoreCase)) + { + $tempScriptLocation = $Location + 'items/psscript/' + } + + if($tempScriptLocation) + { + # Ping and resolve the specified location + $scriptLocation = Resolve-Location -Location $tempScriptLocation ` + -LocationParameterName 'ScriptSourceLocation' ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + } + } + } + + return $scriptLocation +} + +function Get-PublishLocation +{ + [CmdletBinding()] + Param + ( + [Parameter()] + [String] + $Location + ) + + $PublishLocation = $null + + if($Location) + { + # For local dir or SMB-share locations, ScriptPublishLocation is PublishLocation. + if(Microsoft.PowerShell.Management\Test-Path -Path $Location) + { + $PublishLocation = $Location + } + else + { + $tempPublishLocation = $null + + if($Location.EndsWith('/api/v2', [System.StringComparison]::OrdinalIgnoreCase)) + { + $tempPublishLocation = $Location + '/package/' + } + elseif($Location.EndsWith('/api/v2/', [System.StringComparison]::OrdinalIgnoreCase)) + { + $tempPublishLocation = $Location + 'package/' + } + + if($tempPublishLocation) + { + $PublishLocation = $tempPublishLocation + } + } + } + + return $PublishLocation +} + +function Resolve-Location +{ + [CmdletBinding()] + [OutputType([string])] + Param + ( + [Parameter(Mandatory=$true)] + [string] + $Location, + + [Parameter(Mandatory=$true)] + [string] + $LocationParameterName, + + [Parameter()] + [System.Management.Automation.PSCmdlet] + $CallerPSCmdlet + ) + + # Ping and resolve the specified location + if(-not (Test-WebUri -uri $Location)) + { + if(Microsoft.PowerShell.Management\Test-Path -Path $Location) + { + return $Location + } + elseif($CallerPSCmdlet) + { + $message = $LocalizedData.PathNotFound -f ($Location) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "PathNotFound" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Location + } + } + else + { + $pingResult = Ping-Endpoint -Endpoint $Location + $statusCode = $null + $exception = $null + $resolvedLocation = $null + if($pingResult -and $pingResult.ContainsKey($Script:ResponseUri)) + { + $resolvedLocation = $pingResult[$Script:ResponseUri] + } + + if($pingResult -and $pingResult.ContainsKey($Script:StatusCode)) + { + $statusCode = $pingResult[$Script:StatusCode] + } + + Write-Debug -Message "Ping-Endpoint: location=$Location, statuscode=$statusCode, resolvedLocation=$resolvedLocation" + + if((($statusCode -eq 200) -or ($statusCode -eq 401)) -and $resolvedLocation) + { + return $resolvedLocation + } + elseif($CallerPSCmdlet) + { + $message = $LocalizedData.InvalidWebUri -f ($Location, $LocationParameterName) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidWebUri" ` + -CallerPSCmdlet $CallerPSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Location + } + } +} + +function Test-WebUri +{ + [CmdletBinding()] + [OutputType([bool])] + Param + ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $uri + ) + + return ($uri.AbsoluteURI -ne $null) -and ($uri.Scheme -match '[http|https]') +} + +function Test-WildcardPattern +{ + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory=$true)] + [ValidateNotNull()] + $Name + ) + + return [System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Name) +} + +# Utility to throw an errorrecord +function ThrowError +{ + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCmdlet] + $CallerPSCmdlet, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ExceptionName, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ExceptionMessage, + + [System.Object] + $ExceptionObject, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ErrorId, + + [parameter(Mandatory = $true)] + [ValidateNotNull()] + [System.Management.Automation.ErrorCategory] + $ErrorCategory + ) + + $exception = New-Object $ExceptionName $ExceptionMessage; + $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject + $CallerPSCmdlet.ThrowTerminatingError($errorRecord) +} + + +#endregion + +# Create install locations for scripts if they are not already created +if(-not (Microsoft.PowerShell.Management\Test-Path -Path $script:ProgramFilesInstalledScriptInfosPath) -and (Test-RunningAsElevated)) +{ + $null = Microsoft.PowerShell.Management\New-Item -Path $script:ProgramFilesInstalledScriptInfosPath ` + -ItemType Directory ` + -Force ` + -Confirm:$false ` + -WhatIf:$false +} + +if(-not (Microsoft.PowerShell.Management\Test-Path -Path $script:MyDocumentsInstalledScriptInfosPath)) +{ + $null = Microsoft.PowerShell.Management\New-Item -Path $script:MyDocumentsInstalledScriptInfosPath ` + -ItemType Directory ` + -Force ` + -Confirm:$false ` + -WhatIf:$false +} + +Set-Alias -Name fimo -Value Find-Module +Set-Alias -Name inmo -Value Install-Module +Set-Alias -Name upmo -Value Update-Module +Set-Alias -Name pumo -Value Publish-Module +Set-Alias -Name uimo -Value Uninstall-Module + +Export-ModuleMember -Function Find-Module, ` + Save-Module, ` + Install-Module, ` + Update-Module, ` + Publish-Module, ` + Uninstall-Module, ` + Get-InstalledModule, ` + Find-Command, ` + Find-DscResource, ` + Find-RoleCapability, ` + Install-Script, ` + Find-Script, ` + Save-Script, ` + Update-Script, ` + Publish-Script, ` + Get-InstalledScript, ` + Uninstall-Script, ` + Test-ScriptFileInfo, ` + New-ScriptFileInfo, ` + Update-ScriptFileInfo, ` + Get-PSRepository, ` + Register-PSRepository, ` + Unregister-PSRepository, ` + Set-PSRepository, ` + Find-Package, ` + Get-PackageDependencies, ` + Download-Package, ` + Install-Package, ` + Uninstall-Package, ` + Get-InstalledPackage, ` + Remove-PackageSource, ` + Resolve-PackageSource, ` + Add-PackageSource, ` + Get-DynamicOptions, ` + Initialize-Provider, ` + Get-Feature, ` + Get-PackageProviderName, ` + Update-ModuleManifest ` + -Alias fimo, ` + inmo, ` + upmo, ` + pumo diff --git a/src/Microsoft.PowerShell.Linux.Host/Modules/Pester b/src/Modules/Pester similarity index 100% rename from src/Microsoft.PowerShell.Linux.Host/Modules/Pester rename to src/Modules/Pester diff --git a/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs b/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs index f6fb21d0b3..ec69e4fa4c 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs @@ -3,8 +3,6 @@ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ -#if CORECLR - using System.Globalization; using System.Linq; using System.Reflection; @@ -1679,5 +1677,3 @@ namespace Microsoft.PowerShell.CoreCLR } #endif - -#endif diff --git a/src/System.Management.Automation/CoreCLR/CorePsStub.cs b/src/System.Management.Automation/CoreCLR/CorePsStub.cs index e17511684a..319e359ffa 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsStub.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsStub.cs @@ -12,8 +12,6 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Win32; using System.Management.Automation.Remoting; -#if CORECLR - #pragma warning disable 1591, 1572, 1571, 1573, 1587, 1570, 0067 #region CLR_STUBS @@ -1874,5 +1872,3 @@ namespace System #pragma warning restore 1591, 1572, 1571, 1573, 1587, 1570, 0067 #endif - -#endif diff --git a/src/TypeCatalogParser/Main.cs b/src/TypeCatalogParser/Main.cs index 2bca648ef3..4a8fa2f0ae 100644 --- a/src/TypeCatalogParser/Main.cs +++ b/src/TypeCatalogParser/Main.cs @@ -17,7 +17,7 @@ namespace ConsoleApplication var outputPath = "../TypeCatalogGen/powershell.inc"; // Get a context for our top level project - var context = ProjectContext.Create("../Microsoft.PowerShell.Linux.Host", NuGetFramework.Parse("netstandardapp1.5")); + var context = ProjectContext.Create("../Microsoft.PowerShell.Host", NuGetFramework.Parse("netstandardapp1.5")); System.IO.File.WriteAllLines(outputPath, // Get the target for the current runtime diff --git a/src/TypeCatalogParser/project.json b/src/TypeCatalogParser/project.json index f3e2050da0..ed52514f47 100644 --- a/src/TypeCatalogParser/project.json +++ b/src/TypeCatalogParser/project.json @@ -13,5 +13,14 @@ "netstandardapp1.5": { "imports": [ "dnxcore50", "portable-net45+win8" ] } + }, + + "runtimes": { + "ubuntu.14.04-x64": { }, + "centos.7.1-x64": { }, + "win7-x64": { }, + "win10-x64": { }, + "osx.10.10-x64": { }, + "osx.10.11-x64": { } } } diff --git a/src/libpsl-native b/src/libpsl-native deleted file mode 160000 index 4bb241ad11..0000000000 --- a/src/libpsl-native +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4bb241ad11c54985617343fbeb8700cfdcdf7ae0 diff --git a/src/libpsl-native/.gitignore b/src/libpsl-native/.gitignore new file mode 100644 index 0000000000..0dff693725 --- /dev/null +++ b/src/libpsl-native/.gitignore @@ -0,0 +1,12 @@ +CMakeCache.txt +CMakeFiles +CMakeScripts +Makefile +cmake_install.cmake +install_manifest.txt +CTestTestfile.cmake +Testing/ +test/psl-native-test +src/libpsl-native.so +src/libpsl-native.dylib +test/native-tests.xml diff --git a/src/libpsl-native/CMakeLists.txt b/src/libpsl-native/CMakeLists.txt new file mode 100644 index 0000000000..8aa0988875 --- /dev/null +++ b/src/libpsl-native/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.8.4) +project(PSL-NATIVE) + +add_compile_options(-std=c++11 -Wall -Werror) +set(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/../Microsoft.PowerShell.Host") + +# test in BUILD_DIR +enable_testing() +add_subdirectory(src) +add_subdirectory(test) diff --git a/src/libpsl-native/README.md b/src/libpsl-native/README.md new file mode 100644 index 0000000000..930ea3630d --- /dev/null +++ b/src/libpsl-native/README.md @@ -0,0 +1,56 @@ +# libpsl-native + +This library provides functionality missing from .NET Core via system calls, +that are called from from the `CorePsPlatform.cs` file of PowerShell. The +method to do this is a Platform Invoke, which is C#'s Foreign Function +Interface to C code (and C++ by way of `extern C`). + +## Build + +[CMake][] is used to build the project, which results in a `libpsl-native.so` +library on Linux, and `libpsl-native.dylib` on OS X. + +```sh +cmake -DCMAKE_BUILD_TYPE=Debug . +make -j +``` + +[CMake]: https://cmake.org/cmake/help/v2.8.12/cmake.html + +## Test + +The [Google Test][] framework is used for unit tests. + +Use either `make test` or `ctest --verbose` for more output. + +[Google Test]: https://github.com/google/googletest/tree/release-1.7.0 + +## Notes + +Marshalling data from native to managed code is much easier on Linux than it is +on Windows. For instance, to return a string, you simply return a copy of it on +the heap. Since only one memory allocator is used on Linux, the .NET runtime +has no problem later freeing the buffer. Additionally, .NET presumes that the +codepage "Ansi" on Linux is always UTF-8. So just marshal the string as +`UnmanagedType.LPStr`. + +### C# (Managed) + +```c# +[DllImport("libpsl-native", CharSet = CharSet.Ansi)] +[return: MarshalAs(UnmanagedType.LPStr)] +internal static extern string GetSomeString(); +``` + +### C (Native) + +```c +char *GetSomeString() +{ + return strdup("some string"); +} +``` + +The CoreFX team has an excellent guide for [UNIX Interop][]. + +[UNIX Interop]: https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/interop-guidelines.md#unix-shims diff --git a/src/libpsl-native/src/CMakeLists.txt b/src/libpsl-native/src/CMakeLists.txt new file mode 100644 index 0000000000..5b2ec24199 --- /dev/null +++ b/src/libpsl-native/src/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(psl-native SHARED + getcurrentprocessorid.cpp + getusername.cpp + getcomputername.cpp + getlinkcount.cpp + getfullyqualifiedname.cpp + issymlink.cpp + isexecutable.cpp + setdate.cpp + createhardlink.cpp + createsymlink.cpp + followsymlink.cpp) + +target_include_directories(psl-native PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/libpsl-native/src/createhardlink.cpp b/src/libpsl-native/src/createhardlink.cpp new file mode 100644 index 0000000000..2f131efaa5 --- /dev/null +++ b/src/libpsl-native/src/createhardlink.cpp @@ -0,0 +1,115 @@ +//! @file createsymlink.cpp +//! @author George FLeming +//! @brief create new hard link + +#include +#include +#include +#include "createhardlink.h" + +//! @brief Createhardlink create new symbolic link +//! +//! Createhardlink +//! +//! @param[in] link +//! @parblock +//! A pointer to the buffer that contains the symbolic link to create +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @param[in] target +//! @parblock +//! A pointer to the buffer that contains the existing file +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_TOO_MANY_LINK: max number of hard links has been exceeded +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_NO_SUCH_USER: there was no corresponding entry in the utmp-file +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_BUFFER_OVERFLOW: file name is too long +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters +//! +//! @retval 1 if creation is successful +//! @retval 0 if createion failed +//! + +int32_t CreateHardLink(const char *newlink, const char *target) +{ + errno = 0; + + // Check parameters + if (!newlink || !target) + { + errno = ERROR_INVALID_PARAMETER; + return 0; + } + + int returnCode = link(target, newlink); + + if (returnCode == 0) + { + return 1; + } + + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EDQUOT: + errno = ERROR_DISK_FULL; + break; + case EEXIST: + errno = ERROR_FILE_EXISTS; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case EIO: + errno = ERROR_GEN_FAILURE; + break; + case ELOOP: + errno = ERROR_TOO_MANY_LINKS; + break; + case EMLINK: + errno = ERROR_TOO_MANY_LINKS; + break; + case ENAMETOOLONG: + errno = ERROR_BAD_PATH_NAME; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_OUTOFMEMORY; + break; + case ENOTDIR: + errno = ERROR_INVALID_NAME; + break; + case ENOSPC: + errno = ERROR_DISK_FULL; + break; + case EPERM: + errno = ERROR_ACCESS_DENIED; + break; + case EROFS: + errno = ERROR_ACCESS_DENIED; + break; + case EXDEV: + errno = ERROR_GEN_FAILURE; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return 0; +} diff --git a/src/libpsl-native/src/createhardlink.h b/src/libpsl-native/src/createhardlink.h new file mode 100644 index 0000000000..b8e62ddcc2 --- /dev/null +++ b/src/libpsl-native/src/createhardlink.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t CreateHardLink(const char *link, const char *target); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/createsymlink.cpp b/src/libpsl-native/src/createsymlink.cpp new file mode 100644 index 0000000000..4ea8d6fd9d --- /dev/null +++ b/src/libpsl-native/src/createsymlink.cpp @@ -0,0 +1,106 @@ +//! @file createsymlink.cpp +//! @author George FLeming +//! @brief create new symbolic link + +#include +#include +#include +#include "createsymlink.h" + +//! @brief Createsymlink create new symbolic link +//! +//! Createsymlink +//! +//! @param[in] link +//! @parblock +//! A pointer to the buffer that contains the symbolic link to create +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @param[in] target +//! @parblock +//! A pointer to the buffer that contains the existing file +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_STOPPED_ON_SYMLINK: the operation stopped after reaching a symbolic link +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_NO_SUCH_USER: there was no corresponding entry in the utmp-file +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_BUFFER_OVERFLOW: file name is too long +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters +//! +//! @retval 1 if creation is successful +//! @retval 0 if createion failed +//! + +int32_t CreateSymLink(const char *link, const char *target) +{ + errno = 0; + + // Check parameters + if (!link || !target) + { + errno = ERROR_INVALID_PARAMETER; + return 0; + } + + int returnCode = symlink(target, link); + + if (returnCode == 0) + { + return 1; + } + + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EDQUOT: + errno = ERROR_DISK_FULL; + break; + case EEXIST: + errno = ERROR_FILE_EXISTS; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case EIO: + errno = ERROR_GEN_FAILURE; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case ENAMETOOLONG: + errno = ERROR_BAD_PATH_NAME; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_OUTOFMEMORY; + break; + case ENOTDIR: + errno = ERROR_INVALID_NAME; + break; + case ENOSPC: + errno = ERROR_DISK_FULL; + break; + case EPERM: + errno = ERROR_GEN_FAILURE; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return 0; +} diff --git a/src/libpsl-native/src/createsymlink.h b/src/libpsl-native/src/createsymlink.h new file mode 100644 index 0000000000..e47bce3fa1 --- /dev/null +++ b/src/libpsl-native/src/createsymlink.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t CreateSymLink(const char *link, const char *target); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/followsymlink.cpp b/src/libpsl-native/src/followsymlink.cpp new file mode 100644 index 0000000000..40e12b9b78 --- /dev/null +++ b/src/libpsl-native/src/followsymlink.cpp @@ -0,0 +1,90 @@ +//! @file followSymLink.cpp +//! @author George FLeming +//! @brief returns whether a path is a symbolic link + +#include +#include +#include +#include +#include "followsymlink.h" + +//! @brief Followsymlink determines target path of a sym link +//! +//! Followsymlink +//! +//! @param[in] fileName +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_STOPPED_ON_SYMLINK: too many symbolic links +//! - ERROR_GEN_FAILURE: I/O error occurred +//! - ERROR_INVALID_NAME: file provided is not a symbolic link +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long +//! - ERROR_OUTOFMEMORY insufficient kernal memory +//! +//! @retval target path, or NULL if unsuccessful +//! + +char* FollowSymLink(const char* fileName) +{ + errno = 0; + + // Check parameters + if (!fileName) + { + errno = ERROR_INVALID_PARAMETER; + return NULL; + } + + char buffer[PATH_MAX]; + ssize_t sz = readlink(fileName, buffer, PATH_MAX); + + if (sz == -1) + { + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case EINVAL: + errno = ERROR_INVALID_NAME; + break; + case EIO: + errno = ERROR_GEN_FAILURE; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case ENAMETOOLONG: + errno = ERROR_BAD_PATH_NAME; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_OUTOFMEMORY; + break; + case ENOTDIR: + errno = ERROR_BAD_PATH_NAME; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return NULL; + } + + buffer[sz] = '\0'; + return strndup(buffer, sz + 1); +} diff --git a/src/libpsl-native/src/followsymlink.h b/src/libpsl-native/src/followsymlink.h new file mode 100644 index 0000000000..fac7f5244f --- /dev/null +++ b/src/libpsl-native/src/followsymlink.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +char* FollowSymLink(const char* fileName); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getcomputername.cpp b/src/libpsl-native/src/getcomputername.cpp new file mode 100644 index 0000000000..3e480b9ad4 --- /dev/null +++ b/src/libpsl-native/src/getcomputername.cpp @@ -0,0 +1,45 @@ +//! @file getcomputername.cpp +//! @author George Fleming +//! @brief Implements GetComputerName Win32 API + +#include +#include +#include +#include "getcomputername.h" + +//! @brief GetComputerName retrieves the name of the host associated with +//! the current thread. +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_FUNCTION: getlogin_r() returned an unrecognized error code +//! - ERROR_INVALID_ADDRESS: buffer is an invalid address +//! - ERROR_GEN_FAILURE: buffer not large enough +//! +//! @retval username as UTF-8 string, or null if unsuccessful + +char* GetComputerName() +{ + errno = 0; + // Get computername from system, note that gethostname(2) gets the + // nodename from uname + std::string computername(_POSIX_HOST_NAME_MAX, 0); + int err = gethostname(&computername[0], computername.length()); + // Map errno to Win32 Error Codes + if (err != 0) + { + switch (errno) + { + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case ENAMETOOLONG: + errno = ERROR_GEN_FAILURE; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return NULL; + } + + return strdup(computername.c_str()); +} diff --git a/src/libpsl-native/src/getcomputername.h b/src/libpsl-native/src/getcomputername.h new file mode 100644 index 0000000000..363210921b --- /dev/null +++ b/src/libpsl-native/src/getcomputername.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +char *GetComputerName(); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getcurrentprocessorid.cpp b/src/libpsl-native/src/getcurrentprocessorid.cpp new file mode 100644 index 0000000000..eea80937b9 --- /dev/null +++ b/src/libpsl-native/src/getcurrentprocessorid.cpp @@ -0,0 +1,9 @@ +#include "getcurrentprocessorid.h" +#include + +int32_t GetCurrentProcessId() +{ + pid_t pid = getpid(); + return static_cast(pid); +} + diff --git a/src/libpsl-native/src/getcurrentprocessorid.h b/src/libpsl-native/src/getcurrentprocessorid.h new file mode 100644 index 0000000000..3c768e72e5 --- /dev/null +++ b/src/libpsl-native/src/getcurrentprocessorid.h @@ -0,0 +1,10 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t GetCurrentProcessId(); + +PAL_END_EXTERNC + diff --git a/src/libpsl-native/src/getcurrentthreadid.cpp b/src/libpsl-native/src/getcurrentthreadid.cpp new file mode 100644 index 0000000000..efd7a31361 --- /dev/null +++ b/src/libpsl-native/src/getcurrentthreadid.cpp @@ -0,0 +1,9 @@ +#include "getcurrentthreadid.h" +#include +#include + +HANDLE GetCurrentThreadId() +{ + pid_t tid = pthread_self(); + return reinterpret_cast(tid); +} diff --git a/src/libpsl-native/src/getcurrentthreadid.h b/src/libpsl-native/src/getcurrentthreadid.h new file mode 100644 index 0000000000..258c3c397f --- /dev/null +++ b/src/libpsl-native/src/getcurrentthreadid.h @@ -0,0 +1,10 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +HANDLE GetCurrentThreadId(); + +PAL_END_EXTERNC + diff --git a/src/libpsl-native/src/getfullyqualifiedname.cpp b/src/libpsl-native/src/getfullyqualifiedname.cpp new file mode 100644 index 0000000000..44178a0a5a --- /dev/null +++ b/src/libpsl-native/src/getfullyqualifiedname.cpp @@ -0,0 +1,64 @@ +//! @file getfullyqualifiedname.cpp +//! @author George Fleming +//! @brief Implements GetFullyQualifiedName on Linux + +#include +#include +#include +#include +#include "getcomputername.h" +#include "getfullyqualifiedname.h" + +//! @brief GetFullyQualifiedName retrieves the fully qualifed dns name of the host +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_FUNCTION: getlogin_r() returned an unrecognized error code (from GetComputerName) +//! - ERROR_INVALID_ADDRESS: buffer is an invalid address (from GetComputerName) +//! - ERROR_GEN_FAILURE: buffer not large enough (from GetComputerName) +//! - ERROR_BAD_NET_NAME: Cannot determine network short name +//! +//! @retval username as UTF-8 string, or null if unsuccessful +//! + +char *GetFullyQualifiedName() +{ + errno = 0; + + char *computerName = GetComputerName(); + if (computerName == NULL) + { + return NULL; + } + + if (strchr(computerName, '.') != NULL) + { + return computerName; + } + + struct addrinfo hints, *info; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; /*either IPV4 or IPV6*/ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + + /* There are several ways to get the domain name: + * uname(2), gethostbyname(3), resolver(3), getdomainname(2), + * and getaddrinfo(3). Some of these are not portable, some aren't + * POSIX compliant, and some are being deprecated. getaddrinfo seems + * to be the best choice. + */ + if (getaddrinfo(computerName, "http", &hints, &info) != 0) + { + errno = ERROR_BAD_NET_NAME; + return NULL; + } + + // info is actually a link-list. We'll just return the first full name + + char *fullName = strndup(info->ai_canonname, strlen(info->ai_canonname)); + + freeaddrinfo(info); + free(computerName); + return fullName; +} diff --git a/src/libpsl-native/src/getfullyqualifiedname.h b/src/libpsl-native/src/getfullyqualifiedname.h new file mode 100644 index 0000000000..b44ab68fd5 --- /dev/null +++ b/src/libpsl-native/src/getfullyqualifiedname.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +char *GetFullyQualifiedName(); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getlinkcount.cpp b/src/libpsl-native/src/getlinkcount.cpp new file mode 100644 index 0000000000..1b97a41671 --- /dev/null +++ b/src/libpsl-native/src/getlinkcount.cpp @@ -0,0 +1,104 @@ +//! @file getlinkcount.cpp +//! @author George FLeming +//! @brief Retrieve link count of a file + +#include +#include +#include +#include +#include +#include +#include "getlinkcount.h" + +//! @brief GetLinkCount retrieves the file link count (number of hard links) +//! for the given file +//! +//! GetLinkCount +//! +//! @param[in] fileName +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @param[out] count +//! @parblock +//! This function returns the number of hard links associated with this file +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_STOPPED_ON_SYMLINK: the operation stopped after reaching a symbolic link +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_NO_SUCH_USER: there was no corresponding entry in the utmp-file +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_BUFFER_OVERFLOW: file name is too long +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters +//! +//! @retval 1 If the function succeeds, and the variable pointed to by buffer contains +//! infomation about the files +//! @retval 0 If the function fails, the return value is zero. To get +//! extended error information, call GetLastError. +//! + +int32_t GetLinkCount(const char* fileName, int32_t *count) +{ + errno = 0; + + // Check parameters + if (!fileName) + { + errno = ERROR_INVALID_PARAMETER; + return 0; + } + + struct stat statBuf; + + int returnCode = lstat(fileName, &statBuf); + + if (returnCode != 0) + { + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EBADF: + errno = ERROR_FILE_NOT_FOUND; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case ENAMETOOLONG: + errno = ERROR_GEN_FAILURE; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_NO_SUCH_USER; + break; + case ENOTDIR: + errno = ERROR_INVALID_NAME; + break; + case EOVERFLOW: + errno = ERROR_BUFFER_OVERFLOW; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return 0; + } + + *count = statBuf.st_nlink; + return 1; +} diff --git a/src/libpsl-native/src/getlinkcount.h b/src/libpsl-native/src/getlinkcount.h new file mode 100644 index 0000000000..6a3c8b8e41 --- /dev/null +++ b/src/libpsl-native/src/getlinkcount.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t GetLinkCount(const char* fileName, int32_t *count); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getusername.cpp b/src/libpsl-native/src/getusername.cpp new file mode 100644 index 0000000000..0efab96688 --- /dev/null +++ b/src/libpsl-native/src/getusername.cpp @@ -0,0 +1,65 @@ +//! @file getusername.cpp +//! @author Andrew Schwartzmeyer +//! @brief Implements GetUserName for Linux + +#include +#include +#include +#include +#include +#include "getusername.h" + +//! @brief GetUserName retrieves the name of the user associated with +//! the current thread. +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_NO_SUCH_USER: there was no corresponding user +//! - ERROR_GEN_FAILURE: sysconf() or getpwuid() failed for unknown reasons +//! +//! @retval username as UTF-8 string, or null if unsuccessful +char* GetUserName() +{ + errno = 0; + + struct passwd pwd; + struct passwd* result; + // gets the initial suggested size for buf + int buflen = sysconf(_SC_GETPW_R_SIZE_MAX); + if (buflen == -1) + { + errno = ERROR_GEN_FAILURE; + return NULL; + } + std::string buf(buflen, 0); + + // geteuid() gets the effective user ID of the calling process, and is always successful + int ret = getpwuid_r(geteuid(), &pwd, &buf[0], buflen, &result); + + // Map errno to Win32 Error Codes + if (ret) + { + switch (errno) + { + case ENOENT: + case ESRCH: + case EBADF: + case EPERM: + errno = ERROR_NO_SUCH_USER; + break; + default: + errno = ERROR_GEN_FAILURE; + } + return NULL; + } + + // Check if no user matched + if (result == NULL) + { + errno = ERROR_NO_SUCH_USER; + return NULL; + } + + // allocate copy on heap so CLR can free it + return strdup(result->pw_name); +} diff --git a/src/libpsl-native/src/getusername.h b/src/libpsl-native/src/getusername.h new file mode 100644 index 0000000000..1326298f42 --- /dev/null +++ b/src/libpsl-native/src/getusername.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +char* GetUserName(); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/isexecutable.cpp b/src/libpsl-native/src/isexecutable.cpp new file mode 100644 index 0000000000..38f6ad1844 --- /dev/null +++ b/src/libpsl-native/src/isexecutable.cpp @@ -0,0 +1,81 @@ +//! @file isExecutable.cpp +//! @author George FLeming +//! @brief returns whether a file is executable + +#include +#include +#include +#include "isexecutable.h" + +//! @brief IsExecutable determines if path is executable +//! +//! IsExecutable +//! +//! @param[in] fileName +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_INVALID_PARAMETER: parameter to access(2) call is incorrect +//! +//! @retval 1 if path is an executable +//! @retval 0 if path is not a executable +//! @retval -1 If the function fails.. To get extended error information, call GetLastError. +//! + +int32_t IsExecutable(const char* fileName) +{ + errno = 0; + + // Check parameters + if (!fileName) + { + errno = ERROR_INVALID_PARAMETER; + return -1; + } + + int returnCode = access(fileName, X_OK); + + if (returnCode == 0) + { + return 1; + } + + switch(errno) + { + case EACCES: + return 0; + case EBADF: + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case EIO: + case ENOMEM: + errno = ERROR_GEN_FAILURE; + break; + case ENOTDIR: + case ENAMETOOLONG: + errno = ERROR_INVALID_NAME; + break; + case EINVAL: + errno = ERROR_INVALID_PARAMETER; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return -1; +} diff --git a/src/libpsl-native/src/isexecutable.h b/src/libpsl-native/src/isexecutable.h new file mode 100644 index 0000000000..da4e9f8f36 --- /dev/null +++ b/src/libpsl-native/src/isexecutable.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t IsExecutable(const char* fileName); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/issymlink.cpp b/src/libpsl-native/src/issymlink.cpp new file mode 100644 index 0000000000..d4ce011cba --- /dev/null +++ b/src/libpsl-native/src/issymlink.cpp @@ -0,0 +1,96 @@ +//! @file isSymLink.cpp +//! @author George FLeming +//! @brief returns whether a path is a symbolic link + +#include +#include +#include +#include +#include +#include "issymlink.h" + +//! @brief IsSymLink determines if path is a symbolic link +//! +//! IsSymLink +//! +//! @param[in] fileName +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_STOPPED_ON_SYMLINK: the operation stopped after reaching a symbolic link +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_NO_SUCH_USER: there was no corresponding entry in the utmp-file +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_BUFFER_OVERFLOW: file name is too long +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters +//! +//! @retval 1 if path is a symbolic link +//! @retval 0 if path is not a symbolic link +//! @retval -1 If the function fails.. To get extended error information, call GetLastError. +//! + +int32_t IsSymLink(const char* fileName) +{ + + errno = 0; + + // Check parameters + if (!fileName) + { + errno = ERROR_INVALID_PARAMETER; + return -1; + } + + struct stat statBuf; + + int returnCode = lstat(fileName, &statBuf); + + if (returnCode != 0) + { + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EBADF: + errno = ERROR_FILE_NOT_FOUND; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case ENAMETOOLONG: + errno = ERROR_GEN_FAILURE; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_NO_SUCH_USER; + break; + case ENOTDIR: + errno = ERROR_INVALID_NAME; + break; + case EOVERFLOW: + errno = ERROR_BUFFER_OVERFLOW; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return -1; + } + + return S_ISLNK(statBuf.st_mode) ? 1 : 0; +} diff --git a/src/libpsl-native/src/issymlink.h b/src/libpsl-native/src/issymlink.h new file mode 100644 index 0000000000..066a4060c1 --- /dev/null +++ b/src/libpsl-native/src/issymlink.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t IsSymLink(const char* fileName); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/pal.h b/src/libpsl-native/src/pal.h new file mode 100644 index 0000000000..39fb6db82c --- /dev/null +++ b/src/libpsl-native/src/pal.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +#define ERROR_INVALID_PARAMETER 87 +#define ERROR_OUTOFMEMORY 14 +#define ERROR_BAD_ENVIRONMENT 0x0000000A +#define ERROR_TOO_MANY_OPEN_FILES 0x00000004 +#define ERROR_INSUFFICIENT_BUFFER 0x0000007A +#define ERROR_NO_ASSOCIATION 0x00000483 +#define ERROR_NO_SUCH_USER 0x00000525 +#define ERROR_INVALID_FUNCTION 0x00000001 +#define ERROR_INVALID_ADDRESS 0x000001e7 +#define ERROR_GEN_FAILURE 0x0000001F +#define ERROR_ACCESS_DENIED 0x00000005 +#define ERROR_INVALID_NAME 0x0000007B +#define ERROR_STOPPED_ON_SYMLINK 0x000002A9 +#define ERROR_BUFFER_OVERFLOW 0x0000006F +#define ERROR_FILE_NOT_FOUND 0x00000002 +#define ERROR_BAD_PATH_NAME 0x000000A1 +#define ERROR_BAD_NET_NAME 0x00000043 +#define ERROR_DISK_FULL 0x00000070 +#define ERROR_FILE_EXISTS 0x00000050 +#define ERROR_TOO_MANY_LINKS 0x00000476 + +/* +**============================================================================== +** +** PAL_BEGIN_EXTERNC +** PAL_END_EXTERNC +** +**============================================================================== +*/ + +#if defined(__cplusplus) +# define PAL_BEGIN_EXTERNC extern "C" { +# define PAL_END_EXTERNC } +#else +# define PAL_BEGIN_EXTERNC +# define PAL_END_EXTERNC +#endif + diff --git a/src/libpsl-native/src/setdate.cpp b/src/libpsl-native/src/setdate.cpp new file mode 100644 index 0000000000..81bfa3417e --- /dev/null +++ b/src/libpsl-native/src/setdate.cpp @@ -0,0 +1,73 @@ +//! @file setdate.cpp +//! @author George FLeming +//! @brief set local/system date and time + +#include +#include +#include +#include +#include +#include +#include "setdate.h" + +//! @brief SetDate sets the date and time on local computer. You must +//! be super-user to set the time. +//! +//! SetDate +//! +//! @param[in] info +//! @parblock +//! A struct that contains program to execute and its parameters +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_BAD_ENVIRONMENT: locale is not UTF-8 +//! - ERROR_INVALID_PARAMETER: time was not passed in correctly +//! - ERROR_ACCESS_DENIED: you must be super-user to set the date +//! +//! @retval 0 successfully set date +//! @retval -1 if failure occurred. To get extended error information, call GetLastError. +//! + +int32_t SetDate(const SetDateInfo &info) +{ + errno = 0; + + // Select locale from environment + setlocale(LC_ALL, ""); + // Check that locale is UTF-8 + if (nl_langinfo(CODESET) != std::string("UTF-8")) + { + errno = ERROR_BAD_ENVIRONMENT; + return -1; + } + + struct tm bdTime; + struct timeval tv; + + bdTime.tm_year = info.Year - 1900; + bdTime.tm_mon = info.Month - 1; // This is zero-based + bdTime.tm_mday = info.Day; + bdTime.tm_hour = info.Hour; + bdTime.tm_min = info.Minute; + bdTime.tm_sec = info.Second; + bdTime.tm_isdst = info.DST; + + time_t newTime = mktime(&bdTime); + if (newTime == -1) + { + errno = ERROR_INVALID_PARAMETER; + return -1; + } + + tv.tv_sec = newTime; + tv.tv_usec = 0; + + int result = settimeofday(&tv, NULL); + if (result == -1) + { + errno = ERROR_ACCESS_DENIED; + return -1; + } + + return 0; +} diff --git a/src/libpsl-native/src/setdate.h b/src/libpsl-native/src/setdate.h new file mode 100644 index 0000000000..b55a7d1e7a --- /dev/null +++ b/src/libpsl-native/src/setdate.h @@ -0,0 +1,22 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +typedef struct setDateInfo +{ + // the order of members does matter here + int32_t Year; + int32_t Month; + int32_t Day; + int32_t Hour; + int32_t Minute; + int32_t Second; + int32_t Millisecond; + int32_t DST; +} SetDateInfo; + +int32_t SetDate(const SetDateInfo &info); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/test/CMakeLists.txt b/src/libpsl-native/test/CMakeLists.txt new file mode 100644 index 0000000000..4ddc538361 --- /dev/null +++ b/src/libpsl-native/test/CMakeLists.txt @@ -0,0 +1,22 @@ +add_subdirectory(googletest) + +add_executable(psl-native-test + test-locale.cpp + test-getcurrentprocessid.cpp + test-getusername.cpp + test-getcomputername.cpp + test-getlinkcount.cpp + test-getfullyqualifiedname.cpp + test-issymlink.cpp + test-isexecutable.cpp + test-createsymlink.cpp + test-createhardlink.cpp + main.cpp) + +# manually include gtest headers +target_include_directories(psl-native-test PRIVATE ${gtest_SOURCE_DIR}/include) + +target_link_libraries(psl-native-test psl-native gtest) + +add_test(NAME psl-native-test + COMMAND psl-native-test --gtest_output=xml:native-tests.xml) diff --git a/src/libpsl-native/test/googletest b/src/libpsl-native/test/googletest new file mode 160000 index 0000000000..c99458533a --- /dev/null +++ b/src/libpsl-native/test/googletest @@ -0,0 +1 @@ +Subproject commit c99458533a9b4c743ed51537e25989ea55944908 diff --git a/src/libpsl-native/test/main.cpp b/src/libpsl-native/test/main.cpp new file mode 100644 index 0000000000..9bb465e024 --- /dev/null +++ b/src/libpsl-native/test/main.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/libpsl-native/test/test-createhardlink.cpp b/src/libpsl-native/test/test-createhardlink.cpp new file mode 100644 index 0000000000..090864b9f7 --- /dev/null +++ b/src/libpsl-native/test/test-createhardlink.cpp @@ -0,0 +1,91 @@ +//! @file test-createhardlink.cpp +//! @author George Fleming +//! @brief Implements test for CreateHardLink() + +#include +#include +#include +#include "getlinkcount.h" +#include "createhardlink.h" + +using namespace std; + +class CreateHardLinkTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const string fileTemplate = "/tmp/symlinktest.fXXXXXX"; + const string dirTemplate = "/tmp/symlinktest.dXXXXXX"; + const string fileHardLink = "/tmp/symlinktest.flink"; + const string dirHardLink = "/tmp/symlinktest.dlink"; + char *file, *dir; + char fileTemplateBuf[bufSize], dirTemplateBuf[bufSize]; + + CreateHardLinkTest() + { + // since mkstemp and mkdtemp modifies the template string, let's give them writable buffers + strcpy(fileTemplateBuf, fileTemplate.c_str()); + strcpy(dirTemplateBuf, dirTemplate.c_str()); + + // First create a temp file + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + + // Create a temp directory + dir = mkdtemp(dirTemplateBuf); + EXPECT_TRUE(dir != NULL); + + // Create hard link to file + int ret1 = CreateHardLink(fileHardLink.c_str(), file); + EXPECT_EQ(ret1, 1); + + // Create hard link to directory - should fail + int ret2 = CreateHardLink(dirHardLink.c_str(), dir); + EXPECT_EQ(ret2, 0); + } + + ~CreateHardLinkTest() + { + int ret; + + ret = unlink(fileHardLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(file); + EXPECT_EQ(0, ret); + + ret = rmdir(dir); + EXPECT_EQ(0, ret); + } +}; + +TEST_F(CreateHardLinkTest, FilePathNameIsNull) +{ + int retVal = CreateHardLink(NULL, NULL); + EXPECT_EQ(retVal, 0); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(CreateHardLinkTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/symlinktest_invalidFile"; + std::string invalidLink = "/tmp/symlinktest_invalidLink"; + + // make sure neither exists + unlink(invalidFile.c_str()); + unlink(invalidLink.c_str()); + + int retVal = CreateHardLink(invalidLink.c_str(), invalidFile.c_str()); + EXPECT_EQ(retVal, 0); +} + +TEST_F(CreateHardLinkTest, VerifyLinkCount) +{ + int count = 0; + int retVal = GetLinkCount(fileHardLink.c_str(), &count); + EXPECT_EQ(1, retVal); + EXPECT_EQ(2, count); +} + diff --git a/src/libpsl-native/test/test-createsymlink.cpp b/src/libpsl-native/test/test-createsymlink.cpp new file mode 100644 index 0000000000..abc0646c61 --- /dev/null +++ b/src/libpsl-native/test/test-createsymlink.cpp @@ -0,0 +1,117 @@ +//! @file test-createsymlink.cpp +//! @author George Fleming +//! @brief Implements test for CreateSymLink() and FollowSymLink() + +#include +#include +#include +#include "issymlink.h" +#include "createsymlink.h" +#include "followsymlink.h" + +using namespace std; + +class CreateSymLinkTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const string fileTemplate = "/tmp/symlinktest.fXXXXXX"; + const string dirTemplate = "/tmp/symlinktest.dXXXXXX"; + const string fileSymLink = "/tmp/symlinktest.flink"; + const string dirSymLink = "/tmp/symlinktest.dlink"; + char *file, *dir; + char fileTemplateBuf[bufSize], dirTemplateBuf[bufSize]; + + CreateSymLinkTest() + { + // since mkstemp and mkdtemp modifies the template string, let's give them writable buffers + strcpy(fileTemplateBuf, fileTemplate.c_str()); + strcpy(dirTemplateBuf, dirTemplate.c_str()); + + // First create a temp file + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + + // Create a temp directory + dir = mkdtemp(dirTemplateBuf); + EXPECT_TRUE(dir != NULL); + + // Create symbolic link to file + int ret1 = CreateSymLink(fileSymLink.c_str(), file); + EXPECT_EQ(ret1, 1); + + // Create symbolic link to directory + int ret2 = CreateSymLink(dirSymLink.c_str(), dir); + EXPECT_EQ(ret2, 1); + } + + ~CreateSymLinkTest() + { + int ret; + + ret = unlink(fileSymLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(dirSymLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(file); + EXPECT_EQ(0, ret); + + ret = rmdir(dir); + EXPECT_EQ(0, ret); + } +}; + +TEST_F(CreateSymLinkTest, FilePathNameIsNull) +{ + int retVal = CreateSymLink(NULL, NULL); + EXPECT_EQ(retVal, 0); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(CreateSymLinkTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/symlinktest_invalidFile"; + std::string invalidLink = "/tmp/symlinktest_invalidLink"; + + // make sure neither exists + unlink(invalidFile.c_str()); + unlink(invalidLink.c_str()); + + // Linux allows creation of symbolic link that points to an invalid file + int retVal = CreateSymLink(invalidLink.c_str(), invalidFile.c_str()); + EXPECT_EQ(retVal, 1); + + std::string target = FollowSymLink(invalidLink.c_str()); + EXPECT_EQ(target, invalidFile); + + unlink(invalidLink.c_str()); +} + +TEST_F(CreateSymLinkTest, SymLinkToFile) +{ + int retVal = IsSymLink(fileSymLink.c_str()); + EXPECT_EQ(1, retVal); + + std::string target = FollowSymLink(fileSymLink.c_str()); + EXPECT_EQ(target, file); +} + +TEST_F(CreateSymLinkTest, SymLinkToDirectory) +{ + int retVal = IsSymLink(dirSymLink.c_str()); + EXPECT_EQ(1, retVal); + + std::string target = FollowSymLink(dirSymLink.c_str()); + EXPECT_EQ(target, dir); +} + +TEST_F(CreateSymLinkTest, SymLinkAgain) +{ + int retVal = CreateSymLink(fileSymLink.c_str(), file); + EXPECT_EQ(0, retVal); + EXPECT_EQ(ERROR_FILE_EXISTS, errno); +} diff --git a/src/libpsl-native/test/test-getcomputername.cpp b/src/libpsl-native/test/test-getcomputername.cpp new file mode 100644 index 0000000000..9c1d676cbd --- /dev/null +++ b/src/libpsl-native/test/test-getcomputername.cpp @@ -0,0 +1,33 @@ +//! @file test-getcomputername.cpp +//! @author George Fleming +//! @brief Unit tests for GetComputerName + +#include +#include "getcomputername.h" + +//! Test fixture for GetComputerNameTest +class GetComputerNameTest : public ::testing::Test +{ +}; + +TEST_F(GetComputerNameTest, Success) +{ + char expectedComputerName[_POSIX_HOST_NAME_MAX]; + + // the gethostname system call gets the nodename from uname + FILE *fPtr = popen("uname -n", "r"); + ASSERT_TRUE(fPtr != NULL); + + char *linePtr = fgets(expectedComputerName, sizeof(expectedComputerName), fPtr); + ASSERT_TRUE(linePtr != NULL); + + // There's a tendency to have \n at end of fgets string, so remove it before compare + size_t sz = strlen(expectedComputerName); + if (sz > 0 && expectedComputerName[sz - 1] == '\n') + { + expectedComputerName[sz - 1] = '\0'; + } + pclose(fPtr); + + ASSERT_STREQ(GetComputerName(), expectedComputerName); +} diff --git a/src/libpsl-native/test/test-getcurrentprocessid.cpp b/src/libpsl-native/test/test-getcurrentprocessid.cpp new file mode 100644 index 0000000000..a1d10a0bee --- /dev/null +++ b/src/libpsl-native/test/test-getcurrentprocessid.cpp @@ -0,0 +1,16 @@ +#include +#include "getcurrentprocessorid.h" + +// This is a very simple test case to show how tests can be written +TEST(GetCurrentProcessId,simple) +{ + const int32_t currentProcessId = GetCurrentProcessId(); + const pid_t pid = getpid(); + + // first make sure that on this platform those types are of the same size + ASSERT_TRUE(sizeof(int32_t) >= sizeof(pid_t)); + + // now compare the actual values + ASSERT_EQ(currentProcessId,static_cast(pid)); +} + diff --git a/src/libpsl-native/test/test-getcurrentthreadid.cpp b/src/libpsl-native/test/test-getcurrentthreadid.cpp new file mode 100644 index 0000000000..7488d192dd --- /dev/null +++ b/src/libpsl-native/test/test-getcurrentthreadid.cpp @@ -0,0 +1,16 @@ +#include +#include "getcurrentthreadid.h" +#include + +TEST(GetCurrentThreadId,simple) +{ + const HANDLE currentThreadId = GetCurrentThreadId(); + const pid_t tid = pthread_self(); + + // first make sure that on this platform those types are of the same size + ASSERT_TRUE(sizeof(HANDLE) >= sizeof(pid_t)); + + // now compare the actual values + ASSERT_EQ(currentThreadId,reinterpret_cast(tid)); +} + diff --git a/src/libpsl-native/test/test-getfullyqualifiedname.cpp b/src/libpsl-native/test/test-getfullyqualifiedname.cpp new file mode 100644 index 0000000000..7c0353cd1d --- /dev/null +++ b/src/libpsl-native/test/test-getfullyqualifiedname.cpp @@ -0,0 +1,40 @@ +//! @file test-getfullyqualifiedname.cpp +//! @author George Fleming +//! @brief Unit tests for GetFullyQualifiedName + +#include +#include "getfullyqualifiedname.h" +#include +#include +#include +#include + +//! Test fixture for GetComputerNameTest +class GetFullyQualifiedNameTest : public ::testing::Test +{ +}; + +TEST_F(GetFullyQualifiedNameTest, ValidateLinuxGetFullyQualifiedDomainName) +{ + std::string actual(GetFullyQualifiedName()); + + std::string hostname(_POSIX_HOST_NAME_MAX, 0); + ASSERT_FALSE(gethostname(&hostname[0], hostname.length())); + // trim null characters from string + hostname = std::string(hostname.c_str()); + + struct addrinfo hints, *info; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + ASSERT_FALSE(getaddrinfo(hostname.c_str(), "http", &hints, &info)); + + // Compare hostname part of FQDN + ASSERT_EQ(hostname, actual.substr(0, hostname.length())); + + // Compare canonical name to FQDN + ASSERT_EQ(info->ai_canonname, actual); + + freeaddrinfo(info); +} diff --git a/src/libpsl-native/test/test-getlinkcount.cpp b/src/libpsl-native/test/test-getlinkcount.cpp new file mode 100644 index 0000000000..fbe1a001fc --- /dev/null +++ b/src/libpsl-native/test/test-getlinkcount.cpp @@ -0,0 +1,95 @@ +//! @file test-getlinkcount.cpp +//! @author George Fleming +//! @brief Implements test for getLinkCount() + +#include +#include +#include +#include +#include +#include +#include +#include "getlinkcount.h" + +class getLinkCountTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const std::string fileTemplate = "/tmp/createFile.XXXXXX"; + char fileTemplateBuf[bufSize]; + + int32_t count; + char *file; + + getLinkCountTest() + { + // since mkstemp modifies the template string, let's give it writable buffer + strcpy(fileTemplateBuf, fileTemplate.c_str()); + + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + } + + void createFileForTesting(const std::string &theFile) + { + std::ofstream ofs; + ofs.open(theFile, std::ofstream::out); + ofs << "hi there, ms ostc!"; + ofs.close(); + } + + std::string createHardLink(const std::string &origFile) + { + std::string newFile = origFile + "_link"; + int ret = link(origFile.c_str(), newFile.c_str()); + EXPECT_EQ(0, ret); + + return newFile; + } + + void removeFile(const std::string &fileName) + { + int ret = unlink(fileName.c_str()); + EXPECT_EQ(0, ret); + } +}; + +TEST_F(getLinkCountTest, FilePathNameIsNull) +{ + int32_t retVal = GetLinkCount(NULL, &count ); + ASSERT_FALSE(retVal); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(getLinkCountTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/createFile"; + int32_t retVal = GetLinkCount(invalidFile.c_str(), &count); + ASSERT_FALSE(retVal); + EXPECT_EQ(ERROR_FILE_NOT_FOUND, errno); +} + +TEST_F(getLinkCountTest, LinkCountOfSinglyLinkedFile) +{ + createFileForTesting(file); + int32_t retVal = GetLinkCount(file, &count); + ASSERT_TRUE(retVal); + EXPECT_EQ(1, count); + + removeFile(file); +} + +TEST_F(getLinkCountTest, LinkCountOfMultipliLinkedFile) +{ + createFileForTesting(file); + std::string newFile = createHardLink(file); + int32_t retVal = GetLinkCount(file, &count); + ASSERT_TRUE(retVal); + EXPECT_EQ(2, count); + + removeFile(file); + removeFile(newFile); +} + diff --git a/src/libpsl-native/test/test-getusername.cpp b/src/libpsl-native/test/test-getusername.cpp new file mode 100644 index 0000000000..2ccb29f68a --- /dev/null +++ b/src/libpsl-native/test/test-getusername.cpp @@ -0,0 +1,17 @@ +//! @file test-getusername.cpp +//! @author Andrew Schwartzmeyer +//! @brief Unit tests for GetUserName + +#include +#include +#include +#include +#include +#include "getusername.h" + +TEST(GetUserName, Success) +{ + char* expected = getpwuid(geteuid())->pw_name; + ASSERT_TRUE(expected != NULL); + ASSERT_EQ(GetUserName(), std::string(expected)); +} diff --git a/src/libpsl-native/test/test-isexecutable.cpp b/src/libpsl-native/test/test-isexecutable.cpp new file mode 100644 index 0000000000..36955912ff --- /dev/null +++ b/src/libpsl-native/test/test-isexecutable.cpp @@ -0,0 +1,94 @@ +//! @file test-isexecutable.cpp +//! @author George Fleming +//! @brief Implements test for isexecutable() + +#include +#include +#include +#include +#include "isexecutable.h" + +using namespace std; + +class IsExecutableTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const string fileTemplate = "/tmp/isexecutabletest.fXXXXXXX"; + const mode_t mode_700 = S_IRUSR | S_IWUSR | S_IXUSR; + const mode_t mode_070 = S_IRGRP | S_IWGRP | S_IXGRP; + const mode_t mode_007 = S_IROTH | S_IWOTH | S_IXOTH; + const mode_t mode_777 = mode_700 | mode_070 | mode_007; + const mode_t mode_444 = S_IRUSR | S_IRGRP | S_IROTH; + + char *file; + char fileTemplateBuf[bufSize]; + + IsExecutableTest() + { + // since mkstemp modifies the template string, let's give it writable buffers + strcpy(fileTemplateBuf, fileTemplate.c_str()); + + // First create a file + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + } + + ~IsExecutableTest() + { + int ret; + + ret = unlink(file); + EXPECT_EQ(0, ret); + } + + void ChangeFilePermission(const char* file, mode_t mode) + { + int ret = chmod(file, mode); + EXPECT_EQ(ret, 0); + } +}; + +TEST_F(IsExecutableTest, FilePathNameIsNull) +{ + int32_t retVal = IsExecutable(NULL); + EXPECT_EQ(retVal, -1); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(IsExecutableTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/isexecutabletest_invalidFile"; + int32_t retVal = IsExecutable(invalidFile.c_str()); + EXPECT_EQ(retVal, -1); + EXPECT_EQ(ERROR_FILE_NOT_FOUND, errno); +} + +TEST_F(IsExecutableTest, NormalFileIsNotIsexecutable) +{ + int32_t retVal = IsExecutable(file); + EXPECT_EQ(0, retVal); + + ChangeFilePermission(file, mode_444); + + retVal = IsExecutable(file); + EXPECT_EQ(0, retVal); +} + +TEST_F(IsExecutableTest, FilePermission_700) +{ + ChangeFilePermission(file, mode_700); + + int32_t retVal = IsExecutable(file); + EXPECT_EQ(1, retVal); +} + +TEST_F(IsExecutableTest, FilePermission_777) +{ + ChangeFilePermission(file, mode_777); + + int32_t retVal = IsExecutable(file); + EXPECT_EQ(1, retVal); +} diff --git a/src/libpsl-native/test/test-issymlink.cpp b/src/libpsl-native/test/test-issymlink.cpp new file mode 100644 index 0000000000..4aaa3eb541 --- /dev/null +++ b/src/libpsl-native/test/test-issymlink.cpp @@ -0,0 +1,104 @@ +//! @file test-issymlink.cpp +//! @author George Fleming +//! @brief Implements test for isSymLink() + +#include +#include +#include +#include "issymlink.h" + +using namespace std; + +class isSymLinkTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const string fileTemplate = "/tmp/symlinktest.fXXXXXX"; + const string dirTemplate = "/tmp/symlinktest.dXXXXXX"; + const string fileSymLink = "/tmp/symlinktest.flink"; + const string dirSymLink = "/tmp/symlinktest.dlink"; + char *file, *dir; + char fileTemplateBuf[bufSize], dirTemplateBuf[bufSize]; + + isSymLinkTest() + { + // since mkstemp and mkdtemp modifies the template string, let's give them writable buffers + strcpy(fileTemplateBuf, fileTemplate.c_str()); + strcpy(dirTemplateBuf, dirTemplate.c_str()); + + // First create a file + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + + // Create a temp directory + dir = mkdtemp(dirTemplateBuf); + EXPECT_TRUE(dir != NULL); + + // Create symbolic link to file + int ret1 = symlink(file, fileSymLink.c_str()); + EXPECT_EQ(ret1, 0); + + // Create symbolic link to directory + int ret2 = symlink(dir, dirSymLink.c_str()); + EXPECT_EQ(ret2, 0); + } + + ~isSymLinkTest() + { + int ret; + + ret = unlink(fileSymLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(dirSymLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(file); + EXPECT_EQ(0, ret); + + ret = rmdir(dir); + EXPECT_EQ(0, ret); + } +}; + +TEST_F(isSymLinkTest, FilePathNameIsNull) +{ + int retVal = IsSymLink(NULL); + EXPECT_EQ(retVal, -1); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(isSymLinkTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/symlinktest_invalidFile"; + int retVal = IsSymLink(invalidFile.c_str()); + EXPECT_EQ(retVal, -1); + EXPECT_EQ(ERROR_FILE_NOT_FOUND, errno); +} + +TEST_F(isSymLinkTest, NormalFileIsNotSymLink) +{ + int retVal = IsSymLink(file); + EXPECT_EQ(0, retVal); +} + +TEST_F(isSymLinkTest, SymLinkToFile) +{ + int retVal = IsSymLink(fileSymLink.c_str()); + EXPECT_EQ(1, retVal); +} + +TEST_F(isSymLinkTest, NormalDirectoryIsNotSymbLink) +{ + int retVal = IsSymLink(dir); + EXPECT_EQ(0, retVal); +} + +TEST_F(isSymLinkTest, SymLinkToDirectory) +{ + int retVal = IsSymLink(dirSymLink.c_str()); + EXPECT_EQ(1, retVal); +} + diff --git a/src/libpsl-native/test/test-locale.cpp b/src/libpsl-native/test/test-locale.cpp new file mode 100644 index 0000000000..043e98910e --- /dev/null +++ b/src/libpsl-native/test/test-locale.cpp @@ -0,0 +1,21 @@ +//! @file test-locale.cpp +//! @author Alex Jordan +//! @brief Unit tests for linux locale + +#include +#include +#include +#include +#include +//! Test fixture for LocaleTest + +class LocaleTest : public ::testing::Test +{ +}; + +TEST_F(LocaleTest, Success) +{ + setlocale (LC_ALL, ""); + ASSERT_FALSE (nl_langinfo(CODESET) == NULL); + ASSERT_TRUE(nl_langinfo(CODESET) == std::string("UTF-8")); +} diff --git a/src/powershell-native/CMakeLists.txt b/src/powershell-native/CMakeLists.txt index 183988d69f..5811791588 100644 --- a/src/powershell-native/CMakeLists.txt +++ b/src/powershell-native/CMakeLists.txt @@ -3,6 +3,13 @@ project(PowerShell) add_compile_options() +# set the output path for `powershell.exe` +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/../Microsoft.PowerShell.ConsoleHost") +foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +endforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES ) + # set these flags, so build does static linking for msvcr120.dll # otherwise this dll need to be present on the system set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") diff --git a/test/csharp/fixture_AssemblyLoadContext.cs b/test/csharp/fixture_AssemblyLoadContext.cs index 71362729e3..7afc152636 100644 --- a/test/csharp/fixture_AssemblyLoadContext.cs +++ b/test/csharp/fixture_AssemblyLoadContext.cs @@ -1,7 +1,7 @@ using Xunit; using System; using System.Management.Automation; -using Microsoft.PowerShell.Linux.Host; +using Microsoft.PowerShell.Host; // This collection fixture initializes Core PowerShell's AssemblyLoadContext once and only // once. Attempting to initialize in a class level fixture will cause multiple diff --git a/test/csharp/project.json b/test/csharp/project.json index cf66793456..bcef766617 100644 --- a/test/csharp/project.json +++ b/test/csharp/project.json @@ -5,7 +5,7 @@ "authors": [ "andschwa" ], "dependencies": { - "Microsoft.PowerShell.Linux.Host": "1.0.0-*" + "Microsoft.PowerShell.Host": "1.0.0-*" }, "frameworks": { @@ -18,5 +18,15 @@ } }, - "testRunner": "xunit" + "testRunner": "xunit", + + "runtimes": { + "ubuntu.14.04-x64": { }, + "centos.7.1-x64": { }, + "win7-x64": { }, + "win81-x64": { }, + "win10-x64": { }, + "osx.10.10-x64": { }, + "osx.10.11-x64": { } + } } diff --git a/test/csharp/test_FileSystemProvider.cs b/test/csharp/test_FileSystemProvider.cs index 3c720f28a8..c1e7a42f8c 100644 --- a/test/csharp/test_FileSystemProvider.cs +++ b/test/csharp/test_FileSystemProvider.cs @@ -13,7 +13,7 @@ using System.Management.Automation.Provider; using System.Management.Automation.Runspaces; using Microsoft.PowerShell; using Microsoft.PowerShell.Commands; -using Microsoft.PowerShell.Linux.Host; +using Microsoft.PowerShell.Host; namespace PSTests { diff --git a/test/csharp/test_Runspace.cs b/test/csharp/test_Runspace.cs index 0a7b00eb98..3115077d94 100644 --- a/test/csharp/test_Runspace.cs +++ b/test/csharp/test_Runspace.cs @@ -2,7 +2,7 @@ using Xunit; using System; using System.Management.Automation; using System.Management.Automation.Runspaces; -using Microsoft.PowerShell.Linux.Host; +using Microsoft.PowerShell.Host; namespace PSTests { diff --git a/test/csharp/test_SessionState.cs b/test/csharp/test_SessionState.cs index adfc9e7a38..bdc4f01beb 100644 --- a/test/csharp/test_SessionState.cs +++ b/test/csharp/test_SessionState.cs @@ -9,7 +9,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Internal.Host; using System.Management.Automation.Runspaces; using Microsoft.PowerShell; -using Microsoft.PowerShell.Linux.Host; +using Microsoft.PowerShell.Host; namespace PSTests { diff --git a/test/powershell/Add-Type.Tests.ps1 b/test/powershell/Add-Type.Tests.ps1 index 142e7e0f5e..f0a4e7f972 100644 --- a/test/powershell/Add-Type.Tests.ps1 +++ b/test/powershell/Add-Type.Tests.ps1 @@ -1,5 +1,5 @@ Describe "Add-Type" { - It "Should not throw given a simple class definition" { + It "Should not throw given a simple class definition" -Skip { { Add-Type -TypeDefinition "public static class foo { }" } | Should Not Throw } } diff --git a/test/powershell/Json.Tests.ps1 b/test/powershell/Json.Tests.ps1 index 53305d2a0b..978f11f643 100644 --- a/test/powershell/Json.Tests.ps1 +++ b/test/powershell/Json.Tests.ps1 @@ -1,15 +1,6 @@ -# While Core PowerShell does not support the JSON cmdlets, a third -# party C# library, [Json.NET](http://www.newtonsoft.com/json), can be -# loaded into PowerShell and used directly. - # http://www.newtonsoft.com/json/help/html/ParsingLINQtoJSON.htm Describe "Json.NET LINQ Parsing" { - # load third party Json.NET library - $base = [System.AppContext]::BaseDirectory - $path = Join-Path $base Newtonsoft.Json.dll - [Microsoft.PowerShell.CoreCLR.AssemblyExtensions]::LoadFrom($path) - BeforeEach { $jsonFile = Join-Path -Path (Join-Path $PSScriptRoot -ChildPath assets) -ChildPath TestJson.json $jsonData = (Get-Content $jsonFile | Out-String) diff --git a/xunit.sh b/xunit.sh deleted file mode 100755 index 2813b4274f..0000000000 --- a/xunit.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -# Test for build dependencies -hash cmake 2>/dev/null || { echo >&2 "No cmake, please run 'sudo apt-get install cmake'"; exit 1; } -hash g++ 2>/dev/null || { echo >&2 "No g++, please run 'sudo apt-get install g++'"; exit 1; } -hash dotnet 2>/dev/null || { echo >&2 "No dotnet, please visit https://dotnet.github.io/getting-started/"; exit 1; } - -# Test for lock file -test -r test/csharp/project.lock.json || { echo >&2 "Please run 'dotnet restore' to download .NET Core packages"; exit 2; } - -# Run xUnit tests -pushd test/csharp -## Build -dotnet build -c Linux -## Work-around dotnet/cli#753 -cp -r -f ../../src/Microsoft.PowerShell.Linux.Host/{Modules,*.so,*.dylib} bin/Linux/netstandardapp1.5/ubuntu.14.04-x64 2>/dev/null -## Test -dotnet test -c Linux -result=$? -popd - -exit $result