LeetABit.Build.Common.psm1
#requires -version 6 using namespace System.Management.Automation using namespace System.Collections Set-StrictMode -Version 3.0 Import-LocalizedData -BindingVariable LocalizedData -FileName LeetABit.Build.Common.Resources.psd1 ################################################################################################################## # Public Commands ################################################################################################################## function ConvertTo-ExpressionString { <# .SYNOPSIS Converts an object to a PowerShell expression string. .DESCRIPTION The ConvertTo-ExpressionString cmdlet converts any .NET object to a object type's defined string representation. Dictionaries and PSObjects are converted to hash literal expression format. The field and properties are converted to key expressions, the field and properties values are converted to property values, and the methods are removed. Objects that implements IEnumerable are converted to array literal expression format. .EXAMPLE ConvertTo-ExpressionString -Obj $Null, $True, $False $Null $True $False Converts PowerShell literals expression string. .EXAMPLE ConvertTo-ExpressionString -Obj @{Name = "Custom object instance"} @{ 'Name' = 'Custom object instance' } Converts hashtable to PowerShell hash literal expression string. .EXAMPLE ConvertTo-ExpressionString -Obj @( $Name ) @( $Null ) Converts array to PowerShell array literal expression string. .EXAMPLE ConvertTo-ExpressionString -Obj (New-PSObject "SampleType" @{Name = "Custom object instance"}) <# SampleType #`> @{ 'Name' = 'Custom object instance' } Converts custom PSObject to PowerShell hash literal expression string with a custom type name in the comment block. #> [CmdletBinding(PositionalBinding = $False)] [OutputType([String[]])] param ( # Object to convert. [Parameter(HelpMessage = 'Provide an object to convert.', Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [AllowNull()] [Object] $Obj ) process { ConvertTo-ExpressionStringWithIndentation $Obj 0 } } function Import-CallerPreference { <# .SYNOPSIS Fetches "Preference" variable values from the caller's scope. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, but they can be obtained through the $PSCmdlet variable in Advanced Functions. This function is a helper function for any script module Advanced Function; by passing in the values of $PSCmdlet and $ExecutionContext.SessionState, Import-CallerPreference will set the caller's preference variables locally. .EXAMPLE Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Imports the default PowerShell preference variables from the caller into the local scope. .LINK about_Preference_Variables #> [CmdletBinding(PositionalBinding = $False)] param ( # The $PSCmdlet object from a script module Advanced Function. [Parameter(HelpMessage = 'Provide an instance of the $PSCmdlet object.', Position = 0, Mandatory = $True, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $False)] [PSCmdlet] $Cmdlet, # The $ExecutionContext.SessionState object from a script module Advanced Function. # This is how the Import-CallerPreference function sets variables in its callers' scope, # even if that caller is in a different script module. [Parameter(HelpMessage = 'Provide an instance of the $ExecutionContext.SessionState object.', Position = 1, Mandatory = $True, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $False)] [SessionState] $SessionState ) begin { $preferenceVariablesMap = @{ 'ErrorView' = $null 'FormatEnumerationLimit' = $null 'InformationPreference' = $null 'LogCommandHealthEvent' = $null 'LogCommandLifecycleEvent' = $null 'LogEngineHealthEvent' = $null 'LogEngineLifecycleEvent' = $null 'LogProviderHealthEvent' = $null 'LogProviderLifecycleEvent' = $null 'MaximumAliasCount' = $null 'MaximumDriveCount' = $null 'MaximumErrorCount' = $null 'MaximumFunctionCount' = $null 'MaximumHistoryCount' = $null 'MaximumVariableCount' = $null 'OFS' = $null 'OutputEncoding' = $null 'ProgressPreference' = $null 'PSDefaultParameterValues' = $null 'PSEmailServer' = $null 'PSModuleAutoLoadingPreference' = $null 'PSSessionApplicationName' = $null 'PSSessionConfigurationName' = $null 'PSSessionOption' = $null 'ConfirmPreference' = 'Confirm' 'DebugPreference' = 'Debug' 'ErrorActionPreference' = 'ErrorAction' 'VerbosePreference' = 'Verbose' 'WarningPreference' = 'WarningAction' 'WhatIfPreference' = 'WhatIf' } } process { foreach ($variableName in $preferenceVariablesMap.Keys) { $parameterName = $preferenceVariablesMap[$variableName] if (-not $parameterName ` -or ` -not $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName)) { $variable = $Cmdlet.SessionState.PSVariable.Get($variableName) if ($variable) { if ($SessionState -eq $ExecutionContext.SessionState) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } } } } function New-PSObject { <# .SYNOPSIS Creates an instance of a System.Management.Automation.PSObject object. .DESCRIPTION The New-PSObject cmdlet creates an instance of a System.Management.Automation.PSObject object. .EXAMPLE New-PSObject -TypeName "CustomType" -Property @{InstanceName = "Sample instance"} Creates a new custom PSObject with custom type [SampleType] and one property "InstanceName" with value equal to Sample instance". #> [CmdletBinding(PositionalBinding = $False)] [OutputType([PSObject])] param ( # Specifies a custom type name for the object. # Enter a hash table in which the keys are the names of properties or methods and the values are property values or method arguments. New-Object creates the object and sets each property value and invokes each method in the order that they appear in the hash table. # If you specify a property that does not exist on the object, New-PSObject adds the specified property to the object as a NoteProperty. [Parameter(Position = 0, Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)] [String[]] $TypeName, # Sets property values and invokes methods of the new object. [Parameter(Position = 1, Mandatory = $False, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [IDictionary] $Property ) begin { Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState } process { $result = New-Object PSObject -Property $Property if ($PSBoundParameters.ContainsKey('TypeName') -and $TypeName) { foreach ($currentTypeName in $TypeName) { $result.PSObject.TypeNames.Add($currentTypeName) } } $result } } ################################################################################################################## # Private Commands ################################################################################################################## function ConvertTo-ExpressionStringWithIndentation { <# .SYNOPSIS Converts an object to a PowerShell expression string with a specified indentation. .DESCRIPTION The ConvertTo-ExpressionStringWithIndentation cmdlet converts any .NET object to a object type's defined string representation. Dictionaries and PSObjects are converted to hash literal expression format. The field and properties are converted to key expressions, the field and properties values are converted to property values, and the methods are removed. Objects that implements IEnumerable are converted to array literal expression format. Each line of the resulting string is indented by the specified number of spaces. #> [CmdletBinding(PositionalBinding = $False)] [OutputType([String[]])] param ( # Object to convert. [Parameter(HelpMessage = 'Provide an object to convert.', Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [AllowNull()] [AllowEmptyCollection()] [Object] $Obj, # Number of spaces to perpend to each line of the resulting string. [Parameter(HelpMessage = 'Provide an indentation level.', Position = 1, Mandatory = $False, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [ValidateRange([ValidateRangeKind]::NonNegative)] [Int32] $IndentationLevel = 0 ) process { $prefix = " " * $IndentationLevel if ($Null -eq $Obj) { '$Null' } elseif ($Obj -is [String]) { "'$Obj'" } elseif ($Obj -is [SwitchParameter] -or $Obj -is [Boolean]) { "`$$Obj" } elseif ($Obj -is [IDictionary]) { $result = "@{" $Obj.Keys | ForEach-Object { $value = ConvertTo-ExpressionStringWithIndentation $Obj[$_] ($IndentationLevel + 2) $result += [Environment]::NewLine + "$prefix '$_' = $value; " } $result = $result.Substring(0, $result.Length - 2) $result += [Environment]::NewLine + "$prefix}" $result } elseif ($Obj -is [PSCustomObject]) { $result = "" if ($Obj.PSObject.TypeNames.Count -gt 0) { $result += "<# " $Obj.PSObject.TypeNames | ForEach-Object { if ($_ -ne "Selected.System.Management.Automation.PSCustomObject" -and $_ -ne "System.Management.Automation.PSCustomObject" -and $_ -ne "System.Object") { $result += "[$_], " } } $result = $result.Substring(0, $result.Length - 2) $result += " #>" $result += [Environment]::NewLine } $result += "@{" Get-Member -InputObject $Obj -MemberType NoteProperty | ForEach-Object { $value = $Obj | Select-Object -ExpandProperty $_.Name $value = ConvertTo-ExpressionStringWithIndentation $value ($IndentationLevel + 1) $result += [Environment]::NewLine + "$prefix '$($_.Name)' = $value; " } $result = $result.Substring(0, $result.Length - 2) $result += [Environment]::NewLine + "$prefix}" $result } elseif ($Obj -is [IEnumerable]) { $result = "(" $Obj | ForEach-Object { $value = ConvertTo-ExpressionStringWithIndentation $_ ($IndentationLevel + 1) $result += [Environment]::NewLine + "$prefix $value, " } $result = $result.Substring(0, $result.Length - 2) $result += [Environment]::NewLine + "$prefix)" $result } else { [String]$Obj } } } ################################################################################################################## # Classes ################################################################################################################## <# Validates specified argument as a path to a container. #> class ValidateContainerPathAttribute : ValidateArgumentsAttribute { [void] Validate([object]$arguments, [EngineIntrinsics]$engineIntrinsics) { if ([Object]::ReferenceEquals($arguments, $Null)) { throw [System.ArgumentNullException]::new() } $path = [String]$arguments if ([String]::IsNullOrWhiteSpace($path)) { throw [System.ArgumentException]::new('String cannot be empty nor contains only empty spaces.') } Join-Path $path '.' if (-not (Test-Path -Path $path -PathType Container)) { throw [System.ArgumentException]::new("Argument '$path' is not a valid path to an existing container.") } } } <# Validates specified argument as a path to a leaf. #> class ValidateLeafPathAttribute : ValidateArgumentsAttribute { [void] Validate([object]$arguments, [EngineIntrinsics]$engineIntrinsics) { if ([Object]::ReferenceEquals($arguments, $Null)) { throw [System.ArgumentNullException]::new() } $path = [String]$arguments if ([String]::IsNullOrWhiteSpace($path)) { throw [System.ArgumentException]::new('String cannot be empty nor contains only empty spaces.') } Join-Path $path '.' if (-not (Test-Path -Path $path -PathType Leaf)) { throw [System.ArgumentException]::new('Argument is not a valid path to an existing leaf.') } } } <# Validates specified argument as a string of consecutive alphanumeric characters. #> class ValidateIdentifierAttribute : ValidateArgumentsAttribute { [void] Validate([object]$arguments, [EngineIntrinsics]$engineIntrinsics) { if ([Object]::ReferenceEquals($arguments, $Null)) { throw [System.ArgumentNullException]::new() } $identifier = [String]$arguments if ([String]::IsNullOrWhiteSpace($identifier)) { throw [System.ArgumentException]::new('String cannot be empty nor contains only empty spaces.') } if ($identifier -notmatch '^[a-z_][a-z0-9_]*$') { throw [System.ArgumentException]::new('Specified string was not a correct identifier.') } } } <# Validates specified argument as an empty string or string of consecutive alphanumeric characters. #> class ValidateIdentifierOrEmptyAttribute : ValidateArgumentsAttribute { [void] Validate([object]$arguments, [EngineIntrinsics]$engineIntrinsics) { if ([Object]::ReferenceEquals($arguments, $Null)) { throw [System.ArgumentNullException]::new() } $identifier = [String]$arguments if ([String]::IsNullOrEmpty($identifier)) { return } if ([String]::IsNullOrWhiteSpace($identifier)) { throw [System.ArgumentException]::new('String cannot be empty nor contains only empty spaces.') } if ($identifier -notmatch '^[a-z_][a-z0-9_]*$') { throw [System.ArgumentException]::new('Specified string was not a correct identifier.') } } } <# Validates specified argument as a path to a leaf or not existing entry. #> class ValidateNonContainerPathAttribute : ValidateArgumentsAttribute { [void] Validate([object]$arguments, [EngineIntrinsics]$engineIntrinsics) { if ([Object]::ReferenceEquals($arguments, $Null)) { throw [System.ArgumentNullException]::new() } $path = [String]$arguments if ([String]::IsNullOrWhiteSpace($path)) { throw [System.ArgumentException]::new('String cannot be empty nor contains only empty spaces.') } Join-Path $path '.' if (Test-Path -Path $path -PathType Container) { throw [System.ArgumentException]::new('Argument cannot be a path to an existing container.') } } } <# Validates specified argument as a path to a container or not existing entry. #> class ValidateNonLeafPathAttribute : ValidateArgumentsAttribute { [void] Validate([object]$arguments, [EngineIntrinsics]$engineIntrinsics) { if ([Object]::ReferenceEquals($arguments, $Null)) { throw [System.ArgumentNullException]::new() } $path = [String]$arguments if ([String]::IsNullOrWhiteSpace($path)) { throw [System.ArgumentException]::new('String cannot be empty nor contains only empty spaces.') } Join-Path $path '.' if (Test-Path -Path $path -PathType Leaf) { throw [System.ArgumentException]::new('Argument cannot be a path to an existing leaf.') } } } <# Validates specified argument as a PowerShell path. #> class ValidatePathAttribute : ValidateArgumentsAttribute { [void] Validate([object]$arguments, [EngineIntrinsics]$engineIntrinsics) { if ([Object]::ReferenceEquals($arguments, $Null)) { throw [System.ArgumentNullException]::new() } $path = [String]$arguments if ([String]::IsNullOrWhiteSpace($path)) { throw [System.ArgumentException]::new('String cannot be empty nor contains only empty spaces.') } Join-Path $path '.' } } Export-ModuleMember -Function '*' -Variable '*' -Alias '*' -Cmdlet '*' |