Commands/Install-OpenPackage.ps1
|
function Install-OpenPackage { <# .SYNOPSIS Installs an OpenPackage .DESCRIPTION Installs an OpenPackage into a destination on disk. .NOTES Will also install a `.zip` and `.op` of the package to the parent directory of the installation .LINK Expand-Archive #> [CmdletBinding(SupportsShouldProcess,PositionalBinding=$false)] [Alias( 'Install-OP', 'inop', 'inOpenPackage', 'Expand-OpenPackage','Expand-OP', 'enop','enOpenPackage' )] param( # The arguments to Get-OpenPackage. [Parameter(ValueFromRemainingArguments)] [Alias('Arguments','Args','At','Url', 'AtUri', 'FilePath','Repository','Nuget')] [PSObject[]] $ArgumentList, <# The destination path. If provided, this should be a directory, but can be a file. If multiple packages will be installed and a -DestinationPath was provided, all packages will be installed into that destination path. If no destination path is provided, only packages with an identifier will be installed. Packages will install beneath the first `$env:OpenPackagePath`. If the package has a version, it will install into a versioned subdirectory. #> [Parameter()] [ValidateNotNullOrEmpty()] [string] $DestinationPath, <# Includes the specified parts. Enter a wildcard pattern, such as `*.txt` #> [ValidateNotNullOrEmpty()] [SupportsWildcards()] [string[]] $Include, <# Excludes the specified parts. Enter a wildcard pattern, such as `*.txt` #> [ValidateNotNullOrEmpty()] [SupportsWildcards()] [string[]] $Exclude, <# Includes the specified content types. Enter a wildcard pattern, such as `text/*` #> [ValidateNotNullOrEmpty()] [SupportsWildcards()] [string[]] $IncludeContentType, <# Excludes the specified content types. Enter a wildcard pattern, such as `text/*` #> [ValidateNotNullOrEmpty()] [SupportsWildcards()] [string[]] $ExcludeContentType, # The input object. If this is not a package, it will be passed thru. [Parameter(ValueFromPipeline)] [Alias('Package')] [PSObject] $InputObject, # If set, will overwrite existing files. [switch] $Force, # If set, will clear the destination directory before installing. [Alias('Clean')] [switch] $Clear, # If set, will output the files that are expanded from the package. [switch] $PassThru ) # We want to collect all piped input first, # so we can use the implicit `end` block. # And collect all the piped input by enumerating `$input`. $allInput = @($input) # We will be using Select-OpenPackage for filtering, so get a reference to it now. $selectOpenPackage = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Select-OpenPackage','Function') # Get our packages # Each server can have any number of packages # The order packages are defined is the order they are resolved # This allows us to have any number of layers, in any order we want. $packages = @( # First up, lets process our input objects # (piped in objects come first) $remainingInput = @() foreach ($in in $allInput) { # Anything that is a package works if ($in -is [IO.Packaging.Package]) { $in } # so does anything that has a .Package property elseif ( $in.Package -is [IO.Packaging.Package] ) { $in.Package } # anything else we will pipe to Get-OpenPackage else { $remainingInput += $in } } # Now lets check a bound -InputObject # If piped in, this will potentially be a duplicated # (because `$InputObject` will contain the last bound value) foreach ($in in $InputObject) { # Skip any input we already have if ($allInput -contains $in) { continue } # If the -InputObject was a package if ($in -is [IO.Packaging.Package]) { $in # this works } # Otherwise, if the -InputObject has a .Package elseif ( $in.Package -is [IO.Packaging.Package] -and # and it is not a package we already have collected ($allInput.Package -notcontains $in.Package) ) { # then .Package works. $in.Package } # Otherwise, we will pipe remaining input to Get-OpenPackage elseif ($remainingInput -notcontains $in) { $remainingInput += $in } } # If there was remaining input if ($remainingInput) { # pipe it to Get-OpenPackage $remainingInput | Get-OpenPackage @ArgumentList } # If we had arguments, elseif ($ArgumentList) { # call Get-OpenPackage. Get-OpenPackage @ArgumentList } ) # Packages can only be installed once per execution of this function. # So we will need to keep track of already installed packages $alreadyInstalled = @() # We will also want to keep track of what we have cleared, so we can install layers. $Cleared = @() # Keep track of any files we might overwrite $existingFiles = @() # Go over each package we may have foreach ($package in $packages) { # skip anything that is not a package if ($package -isnot [IO.Packaging.Package]) { continue } # or that has already been installed if ($alreadyInstalled -contains $package) { continue } # If no DestinationPath was provided if (-not $PSBoundParameters['DestinationPath']) { # Check for $env:OpenPackagePath if (-not $env:OpenPackagePath) { # error out if missing. Write-Error '$env:OpenPackagePath not defined' return } # If there is no identifier if (-not $package.Identifier) { # error out Write-Error "Must provide -DestinationPath or have a package identifier" return } # Set the destionation path $PSBoundParameters['DestinationPath'] = $destinationPath = Join-Path ( # based off of the $env:OpenPackagePath @($env:OpenPackagePath -split $( if (-not ($IsLinux -or $IsMacOS)) { ';' } else { ':' } ))[0] ) $package.Identifier # and the identifier # If the package had a version if ($package.Version) { # put it within the versioned directory $PSBoundParameters['DestinationPath'] = $destinationPath = Join-Path $DestinationPath $package.Version } } # Copy our parameters to Select-OpenPackage $selectSplat = [Ordered]@{InputObject=$package} foreach ($key in $PSBoundParameters.Keys) { if ($selectOpenPackage.Parameters[$key]) { $selectSplat[$key] = $PSBoundParameters[$key] } } # Get all of the package parts $inputParts = @(Select-OpenPackage @selectSplat) # Now let's prepare our progress bars $total = $inputParts.Length $counter = 0 $Progress = [Ordered]@{ Activity = "Expanding $($package.PackageProperties.Identifier)" Id = Get-Random } # If the destination path exist and has not been cleared if ( (Test-Path $DestinationPath) -and $Cleared -notcontains $DestinationPath ) { # Clear it if we want to if ($Clear -and $psCmdlet.ShouldProcess("Clear $destinationPath")) { Remove-Item -ErrorAction Ignore -Path $DestinationPath -Recurse -Force:$Clear } else { # and warn if we do not. Write-Warning "$DestinationPath exists. Use -Clear to clear the directory." } # Add it to the cleared directories either way, so we do not over warn. $cleared += $DestinationPath } # Go over each part :nextPart foreach ($part in $inputParts) { # Find their destination on disk $partDestination = Join-Path $DestinationPath ([Web.HttpUtility]::UrlDecode($part.Uri)) $counter++ $Progress.Status = $part.Uri $Progress.PercentComplete = $counter * 100 / $total # and write progress. Write-Progress @Progress # Then check if it exists. $fileInfo = if ((Test-Path -LiteralPath $partDestination)) { if (-not $Force) { # We will warn when we're done, # but don't -Force the point by warning each time. # Add it to the list of existing files $existingFile = [IO.FileInfo]"$partDestination" if ($existingFile) { $existingFiles += $existingFile if ($passThru) { $PSCmdlet.WriteObject($existingFiles[-1]) } } continue nextPart # (and continue to the next part). } New-Item -ItemType File -Path $partDestination -Force } else { # create a file if it did not exist. New-Item -ItemType File -Path $partDestination -Force } # If we do not have a file, if ($fileInfo -isnot [IO.FileInfo]) { # continue to the next part continue nextPart } # Open the file for write $fileStream = $fileInfo.OpenWrite() # and continue if that did not work for any reason (for example, the file being locked) if (-not $?) { continue nextPart } # Get the part stream $partStream = $part.GetStream() # copy it to the file $partStream.CopyTo($fileStream) # and close and dispose of them both $fileStream.Close() $fileStream.Dispose() $partStream.Close() $partStream.Dispose() # If we are passing thru if ($PassThru) { # get the exported files. Get-Item -LiteralPath $fileInfo.FullName | Add-Member NoteProperty Package $InputObject -Force -PassThru | Add-Member NoteProperty PartUri $part.Uri -Force -PassThru | Add-Member NoteProperty PartContentType $part.ContentType -Force -PassThru } } # After we have expanded all of the parts $Progress.Remove('PercentComplete') $Progress.Completed = $true # complete our progress Write-Progress @Progress # Mark this package as installed $alreadyInstalled += $package } if ($existingFiles) { Write-Warning "$($existingFiles.Length) Files Exist (Use ``-Force`` to overwrite):$( [Environment]::NewLine $existingFiles -join [Environment]::NewLine )" } } |