Lib/Internal.ps1

function ResolvePathEx {
<#
    .SYNOPSIS
        Resolves the wildcard characters in a path, and displays the path contents, ignoring non-existent paths.
    .DESCRIPTION
        The Resolve-Path cmdlet interprets the wildcard characters in a path and displays the items and containers at
        the location specified by the path, such as the files and folders or registry keys and subkeys.
#>

    [CmdletBinding()]
    [OutputType([System.String])]
    param (
        [Parameter(Mandatory)]
        [System.String] $Path
    )
    process {
        try {
            $expandedPath = [System.Environment]::ExpandEnvironmentVariables($Path);
            $resolvedPath = Resolve-Path -Path $expandedPath -ErrorAction Stop;
            $Path = $resolvedPath.ProviderPath;
        }
        catch [System.Management.Automation.ItemNotFoundException] {
            $Path = [System.Environment]::ExpandEnvironmentVariables($_.TargetObject);
            $Error.Remove($Error[-1]);
        }
        return $Path;
    } #end process
} #end function ResolvePathEx

function InvokeExecutable {
<#
    .SYNOPSIS
        Runs an executable and redirects StdOut and StdErr.
#>

    [CmdletBinding()]
    [OutputType([System.Int32])]
    param (
        # Executable path
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()]
        [System.String] $Path,

        # Executable arguments
        [Parameter(Mandatory)] [ValidateNotNull()]
        [System.Array] $Arguments,

        # Redirected StdOut and StdErr log name
        [Parameter()] [ValidateNotNullOrEmpty()]
        [System.String] $LogName = ('{0}.log' -f $Path)
    )
    process {
        $processArgs = @{
            FilePath = $Path;
            ArgumentList = $Arguments;
            Wait = $true;
            RedirectStandardOutput = '{0}\{1}-StdOut.log' -f $env:temp, $LogName;
            RedirectStandardError = '{0}\{1}-StdErr.log' -f $env:temp, $LogName;
            NoNewWindow = $true;
            PassThru = $true;
        }
        Write-Debug -Message ($localized.RedirectingOutput -f 'StdOut', $processArgs.RedirectStandardOutput);
        Write-Debug -Message ($localized.RedirectingOutput -f 'StdErr', $processArgs.RedirectStandardError);
        WriteVerbose ($localized.StartingProcess -f $Path, [System.String]::Join(' ', $Arguments));
        $process = Start-Process @processArgs;
        if ($process.ExitCode -ne 0) {
            WriteWarning ($localized.ProcessExitCode -f $Path, $process.ExitCode)
        }
        else {
            WriteVerbose ($localized.ProcessExitCode -f $Path, $process.ExitCode);
        }
        ##TODO: Should this actually return the exit code?!
    } #end process
} #end function InvokeExecutable

function GetFormattedMessage {
<#
    .SYNOPSIS
        Generates a formatted output message.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.String] $Message
    )
    process {
        if (($labDefaults.CallStackLogging) -and ($labDefaults.CallStackLogging -eq $true)) {
            $parentCallStack = (Get-PSCallStack)[1]; # store the parent Call Stack
            $functionName = $parentCallStack.FunctionName;
            $lineNumber = $parentCallStack.ScriptLineNumber;
            $scriptName = ($parentCallStack.Location -split ':')[0];
            $formattedMessage = '[{0}] [Script:{1}] [Function:{2}] [Line:{3}] {4}' -f (Get-Date).ToLongTimeString(), $scriptName, $functionName, $lineNumber, $Message;
        }
        else {
            $formattedMessage = '[{0}] {1}' -f (Get-Date).ToLongTimeString(), $Message;
        }
        return $formattedMessage;
    } #end process
} #end function GetFormattedMessage

function WriteVerbose {
<#
    .SYNOPSIS
        Wrapper around Write-Verbose that adds a timestamp and/or call stack information to the output.
#>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)] [AllowNull()]
        [System.String] $Message
    )
    process {
        if (-not [System.String]::IsNullOrEmpty($Message)) {
            $verboseMessage = GetFormattedMessage -Message $Message;
            Write-Verbose -Message $verboseMessage;
        }
    }
} #end function WriteVerbose

function WriteWarning {
<#
    .SYNOPSIS
        Wrapper around Write-Warning that adds a timestamp and/or call stack information to the output.
#>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)] [AllowNull()]
        [System.String] $Message
    )
    process {
        if (-not [System.String]::IsNullOrEmpty($Message)) {
            $warningMessage = GetFormattedMessage -Message $Message;
            Write-Warning -Message $warningMessage;
        }
    }
} #end function WriteWarning

function ConvertPSObjectToHashtable {
<#
    .SYNOPSIS
        Converts a PSCustomObject's properties to a hashtable.
#>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param (
        ## Object to convert to a hashtable
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSObject[]] $InputObject,

        ## Do not add empty/null values to the generated hashtable
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $IgnoreNullValues
    )
    process {
        foreach ($object in $InputObject) {
            $hashtable = @{ }
            foreach ($property in $object.PSObject.Properties) {

                if ($IgnoreNullValues) {
                    if ([System.String]::IsNullOrEmpty($property.Value)) {
                        ## Ignore empty strings
                        continue;
                    }
                }

                if ($property.TypeNameOfValue -eq 'System.Management.Automation.PSCustomObject') {
                    ## Convert nested custom objects to hashtables
                    $hashtable[$property.Name] = ConvertPSObjectToHashtable -InputObject $property.Value -IgnoreNullValues:$IgnoreNullValues;
                }
                else {
                    $hashtable[$property.Name] = $property.Value;
                }

            } #end foreach property
            Write-Output $hashtable;
        }
    } #end proicess
} #end function ConvertPSObjectToHashtable

function CopyDirectory {
<#
    .SYNOPSIS
        Copies a directory structure with progress.
#>

    [CmdletBinding()]
    param (
        ## Source directory path
        [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()]
        [System.IO.DirectoryInfo] $SourcePath,

        ## Destination directory path
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNull()]
        [System.IO.DirectoryInfo] $DestinationPath,

        [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()]
        [System.Management.Automation.SwitchParameter] $Force
    )
    begin {
        if ((Get-Item $SourcePath) -isnot [System.IO.DirectoryInfo]) {
            throw ($localized.CannotProcessArguentError -f 'CopyDirectory', 'SourcePath', $SourcePath, 'System.IO.DirectoryInfo');
        }
        elseif (Test-Path -Path $SourcePath -PathType Leaf) {
            throw ($localized.InvalidDestinationPathError -f $DestinationPath);
        }
    }
    process {
        $activity = $localized.CopyingResource -f $SourcePath.FullName, $DestinationPath;
        $status = $localized.EnumeratingPath -f $SourcePath;
        Write-Progress -Activity $activity -Status $status -PercentComplete 0;
        $fileList = Get-ChildItem -Path $SourcePath -File -Recurse;
        $currentDestinationPath = $SourcePath;

        for ($i = 0; $i -lt $fileList.Count; $i++) {
            if ($currentDestinationPath -ne $fileList[$i].DirectoryName) {
                ## We have a change of directory
                $destinationDirectoryName = $fileList[$i].DirectoryName.Substring($SourcePath.FullName.Trim('\').Length);
                $destinationDirectoryPath = Join-Path -Path $DestinationPath -ChildPath $destinationDirectoryName;
                [ref] $null = New-Item -Path $destinationDirectoryPath -ItemType Directory -ErrorAction Ignore;
                $currentDestinationPath = $fileList[$i].DirectoryName;
            }
           if (($i % 5) -eq 0) {
                [System.Int16] $percentComplete = (($i + 1) / $fileList.Count) * 100;
                $status = $localized.CopyingResourceStatus -f $i, $fileList.Count, $percentComplete;
                Write-Progress -Activity $activity -Status $status -PercentComplete $percentComplete;
            }

            $targetPath = Join-Path -Path $DestinationPath -ChildPath $fileList[$i].FullName.Replace($SourcePath, '');
            Copy-Item -Path $fileList[$i].FullName -Destination $targetPath -Force:$Force;
        } #end for

        Write-Progress -Activity $activity -Completed;
    } #end process
} #end function CopyDirectory

function TestComputerName {
<#
    .SYNOPSIS
        Validates a computer name is valid.
#>

    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param (
        ## Source directory path
        [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()]
        [System.String] $ComputerName
    )
    process {
        $invalidMatch = '[~!@#\$%\^&\*\(\)=\+_\[\]{}\\\|;:.''",<>\/\?\s]';
        return ($ComputerName -inotmatch $invalidMatch);
    }
} #end function TestComputerName