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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Publish Module
on:
pull_request:
push:
branches: [ master ]
workflow_dispatch:
jobs:
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// -------- Search configuration --------
// Exclude the Output folder from search results.
"search.exclude": {
"Output": true,
"Output/**": true
},
//-------- PowerShell Configuration --------
// Use a custom PowerShell Script Analyzer settings file for this workspace.
Expand Down
17 changes: 17 additions & 0 deletions Plaster/Private/ConvertTo-DestinationRelativePath.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function ConvertTo-DestinationRelativePath {
param(
[ValidateNotNullOrEmpty()]
[string]$Path
)
$fullDestPath = $DestinationPath
if (![System.IO.Path]::IsPathRooted($fullDestPath)) {
$fullDestPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($DestinationPath)
}

$fullPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path)
if (!$fullPath.StartsWith($fullDestPath, 'OrdinalIgnoreCase')) {
throw ($LocalizedData.ErrorPathMustBeUnderDestPath_F2 -f $fullPath, $fullDestPath)
}

$fullPath.Substring($fullDestPath.Length).TrimStart('\', '/')
}
82 changes: 82 additions & 0 deletions Plaster/Private/Copy-FileWithConflictDetection.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<#
Plaster zen for file handling. All file related operations should use this
method to actually write/overwrite/modify files in the DestinationPath. This
method handles detecting conflicts, gives the user a chance to determine how to
handle conflicts. The user can choose to use the Force parameter to force the
overwriting of existing files at the destination path. File processing
(expanding substitution variable, modifying file contents) should always be done
to a temp file (be sure to always remove temp file when done). That temp file is
what gets passed to this function as the $SrcPath. This allows Plaster to alert
the user when the repeated application of a template will modify any existing
file.

NOTE: Plaster keeps track of which files it has "created" (as opposed to
overwritten) so that any later change to that file doesn't trigger conflict
handling.
#>
function Copy-FileWithConflictDetection {
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[string]$SrcPath,
[string]$DstPath
)
# Just double-checking that DstPath parameter is an absolute path otherwise
# it could fail the check that the DstPath is under the overall DestinationPath.
if (![System.IO.Path]::IsPathRooted($DstPath)) {
$DstPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($DstPath)
}

# Check if DstPath file conflicts with an existing SrcPath file.
$operation = $LocalizedData.OpCreate
$opMessage = ConvertTo-DestinationRelativePath $DstPath
if (Test-Path -LiteralPath $DstPath) {
if (Test-FilesIdentical $SrcPath $DstPath) {
$operation = $LocalizedData.OpIdentical
} elseif ($script:templateCreatedFiles.ContainsKey($DstPath)) {
# Plaster created this file previously during template invocation
# therefore, there is no conflict. We're simply updating the file.
$operation = $LocalizedData.OpUpdate
} elseif ($Force) {
$operation = $LocalizedData.OpForce
} else {
$operation = $LocalizedData.OpConflict
}
}

# Copy the file to the destination
if ($PSCmdlet.ShouldProcess($DstPath, $operation)) {
Write-OperationStatus -Operation $operation -Message $opMessage

if ($operation -eq $LocalizedData.OpIdentical) {
# If the files are identical, no need to do anything
return
}

if (
($operation -eq $LocalizedData.OpCreate) -or
($operation -eq $LocalizedData.OpUpdate)
) {
Copy-Item -LiteralPath $SrcPath -Destination $DstPath
if ($PassThru) {
$InvokePlasterInfo.CreatedFiles += $DstPath
}
$script:templateCreatedFiles[$DstPath] = $null
} elseif (
$Force -or
$PSCmdlet.ShouldContinue(
($LocalizedData.OverwriteFile_F1 -f $DstPath),
$LocalizedData.FileConflict,
[ref]$script:fileConflictConfirmYesToAll,
[ref]$script:fileConflictConfirmNoToAll
)
) {
$backupFilename = New-BackupFilename $DstPath
Copy-Item -LiteralPath $DstPath -Destination $backupFilename
Copy-Item -LiteralPath $SrcPath -Destination $DstPath
if ($PassThru) {
$InvokePlasterInfo.UpdatedFiles += $DstPath
}
$script:templateCreatedFiles[$DstPath] = $null
}
}
}
65 changes: 65 additions & 0 deletions Plaster/Private/Expand-FileSourceSpec.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
function Expand-FileSourceSpec {
[CmdletBinding()]
param(
[string]$SourceRelativePath,
[string]$DestinationRelativePath
)
$srcPath = Join-Path $templateAbsolutePath $SourceRelativePath
$dstPath = Join-Path $destinationAbsolutePath $DestinationRelativePath

if ($SourceRelativePath.IndexOfAny([char[]]('*', '?')) -lt 0) {
# No wildcard spec in srcRelPath so return info on single file.
# Also, if dstRelPath is empty, then use source rel path.
if (!$DestinationRelativePath) {
$dstPath = Join-Path $destinationAbsolutePath $SourceRelativePath
}

return (New-FileSystemCopyInfo $srcPath $dstPath)
}

# Prepare parameter values for call to Get-ChildItem to get list of files
# based on wildcard spec.
$gciParams = @{}
$parent = Split-Path $srcPath -Parent
$leaf = Split-Path $srcPath -Leaf
$gciParams['LiteralPath'] = $parent
$gciParams['File'] = $true

if ($leaf -eq '**') {
$gciParams['Recurse'] = $true
} else {
if ($leaf.IndexOfAny([char[]]('*', '?')) -ge 0) {
$gciParams['Filter'] = $leaf
}

$leaf = Split-Path $parent -Leaf
if ($leaf -eq '**') {
$parent = Split-Path $parent -Parent
$gciParams['LiteralPath'] = $parent
$gciParams['Recurse'] = $true
}
}

$srcRelRootPathLength = $gciParams['LiteralPath'].Length

# Generate a FileCopyInfo object for every file expanded by the wildcard spec.
$files = @(Microsoft.PowerShell.Management\Get-ChildItem @gciParams)
foreach ($file in $files) {
$fileSrcPath = $file.FullName
$relPath = $fileSrcPath.Substring($srcRelRootPathLength)
$fileDstPath = Join-Path $dstPath $relPath
New-FileSystemCopyInfo $fileSrcPath $fileDstPath
}

# Copy over empty directories - if any.
$gciParams.Remove('File')
$gciParams['Directory'] = $true
$dirs = @(Microsoft.PowerShell.Management\Get-ChildItem @gciParams |
Where-Object { $_.GetFileSystemInfos().Length -eq 0 })
foreach ($dir in $dirs) {
$dirSrcPath = $dir.FullName
$relPath = $dirSrcPath.Substring($srcRelRootPathLength)
$dirDstPath = Join-Path $dstPath $relPath
New-FileSystemCopyInfo $dirSrcPath $dirDstPath
}
}
16 changes: 16 additions & 0 deletions Plaster/Private/Get-ColorForOperation.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
function Get-ColorForOperation {
param(
$operation
)
switch ($operation) {
$LocalizedData.OpConflict { 'Red' }
$LocalizedData.OpCreate { 'Green' }
$LocalizedData.OpForce { 'Yellow' }
$LocalizedData.OpIdentical { 'Cyan' }
$LocalizedData.OpModify { 'Magenta' }
$LocalizedData.OpUpdate { 'Green' }
$LocalizedData.OpMissing { 'Red' }
$LocalizedData.OpVerify { 'Green' }
default { $Host.UI.RawUI.ForegroundColor }
}
}
7 changes: 7 additions & 0 deletions Plaster/Private/Get-ErrorLocationFileAttrVal.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function Get-ErrorLocationFileAttrVal {
param(
[string]$ElementName,
[string]$AttributeName
)
$LocalizedData.ExpressionErrorLocationFile_F2 -f $ElementName, $AttributeName
}
6 changes: 6 additions & 0 deletions Plaster/Private/Get-ErrorLocationModifyAttrVal.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function Get-ErrorLocationModifyAttrVal {
param(
[string]$AttributeName
)
$LocalizedData.ExpressionErrorLocationModify_F1 -f $AttributeName
}
6 changes: 6 additions & 0 deletions Plaster/Private/Get-ErrorLocationNewModManifestAttrVal.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function Get-ErrorLocationNewModManifestAttrVal {
param(
[string]$AttributeName
)
$LocalizedData.ExpressionErrorLocationNewModManifest_F1 -f $AttributeName
}
7 changes: 7 additions & 0 deletions Plaster/Private/Get-ErrorLocationParameterAttrVal.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function Get-ErrorLocationParameterAttrVal {
param(
[string]$ParameterName,
[string]$AttributeName
)
$LocalizedData.ExpressionErrorLocationParameter_F2 -f $ParameterName, $AttributeName
}
7 changes: 7 additions & 0 deletions Plaster/Private/Get-ErrorLocationRequireModuleAttrVal.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function Get-ErrorLocationRequireModuleAttrVal {
param(
[string]$ModuleName,
[string]$AttributeName
)
$LocalizedData.ExpressionErrorLocationRequireModule_F2 -f $ModuleName, $AttributeName
}
25 changes: 25 additions & 0 deletions Plaster/Private/Get-GitConfigValue.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
function Get-GitConfigValue {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$name
)
# Very simplistic git config lookup
# Won't work with namespace, just use final element, e.g. 'name' instead of 'user.name'

# The $Home dir may not be reachable e.g. if on network share and/or script not running as admin.
# See issue https://github.com/PowerShell/Plaster/issues/92
if (!(Test-Path -LiteralPath $Home)) {
return
}

$gitConfigPath = Join-Path $Home '.gitconfig'
$PSCmdlet.WriteDebug("Looking for '$name' value in Git config: $gitConfigPath")

if (Test-Path -LiteralPath $gitConfigPath) {
$matches = Select-String -LiteralPath $gitConfigPath -Pattern "\s+$name\s+=\s+(.+)$"
if (@($matches).Count -gt 0) {
$matches.Matches.Groups[1].Value
}
}
}
61 changes: 61 additions & 0 deletions Plaster/Private/Get-ManifestsUnderPath.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
function Get-ManifestsUnderPath {
<#
.SYNOPSIS
Retrieves Plaster manifest files under a specified path.

.DESCRIPTION
This function searches for Plaster manifest files (`plasterManifest.xml`)
under a specified root path and returns template objects created from those
manifests.

.PARAMETER RootPath
The root path to search for Plaster manifest files.

.PARAMETER Recurse
Whether to search subdirectories for manifest files.

.PARAMETER Name
The name of the template to retrieve.
If not specified, all templates will be returned.

.PARAMETER Tag
The tag of the template to retrieve.
If not specified, templates with any tag will be returned.

.EXAMPLE
Get-ManifestsUnderPath -RootPath "C:\Templates" -Recurse -Name "MyTemplate" -Tag "Tag1"

Retrieves all Plaster templates named "MyTemplate" with the tag "Tag1"
under the "C:\Templates" directory and its subdirectories.

.NOTES
This is a private function used internally by Plaster to manage templates.
It is not intended for direct use by end users.
#>
[CmdletBinding()]
param(
[string]
$RootPath,
[bool]
$Recurse,
[string]
$Name,
[string]
$Tag
)
$getChildItemSplat = @{
Path = $RootPath
Include = "plasterManifest.xml"
Recurse = $Recurse
}
$manifestPaths = Get-ChildItem @getChildItemSplat
foreach ($manifestPath in $manifestPaths) {
$newTemplateObjectFromManifestSplat = @{
ManifestPath = $manifestPath
Name = $Name
Tag = $Tag
ErrorAction = 'SilentlyContinue'
}
New-TemplateObjectFromManifest @newTemplateObjectFromManifestSplat
}
}
15 changes: 15 additions & 0 deletions Plaster/Private/Get-MaxOperationLabelLength.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function Get-MaxOperationLabelLength {
[CmdletBinding()]
[OutputType([int])]
param()
(
$LocalizedData.OpCreate,
$LocalizedData.OpIdentical,
$LocalizedData.OpConflict,
$LocalizedData.OpForce,
$LocalizedData.OpMissing,
$LocalizedData.OpModify,
$LocalizedData.OpUpdate,
$LocalizedData.OpVerify |
Measure-Object -Property Length -Maximum).Maximum
}
12 changes: 12 additions & 0 deletions Plaster/Private/Get-PSSnippetFunction.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function Get-PSSnippetFunction {
param(
[String]$FilePath
)

# Test if Path Exists
if (!(Test-Path $substitute -PathType Leaf)) {
throw ($LocalizedData.ErrorPathDoesNotExist_F1 -f $FilePath)
}
# Load File
return Get-Content -LiteralPath $substitute -Raw
}
32 changes: 32 additions & 0 deletions Plaster/Private/Invoke-ExpressionImpl.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
function Invoke-ExpressionImpl {
[CmdletBinding()]
param (
[string]$Expression
)
try {
$powershell = [PowerShell]::Create()

if ($null -eq $script:constrainedRunspace) {
$script:constrainedRunspace = New-ConstrainedRunspace
}
$powershell.Runspace = $script:constrainedRunspace

try {
$powershell.AddScript($Expression) > $null
$res = $powershell.Invoke()
$res
} catch {
throw ($LocalizedData.ExpressionInvalid_F2 -f $Expression, $_)
}

# Check for non-terminating errors.
if ($powershell.Streams.Error.Count -gt 0) {
$err = $powershell.Streams.Error[0]
throw ($LocalizedData.ExpressionNonTermErrors_F2 -f $Expression, $err)
}
} finally {
if ($powershell) {
$powershell.Dispose()
}
}
}
Loading
Loading