CommonUtilities.psm1

<#
.Synopsis
    Generates a cryptographically secure password.

.Description
    The cmdlet generates strong passwords with cryptographically secure random number generator.

    By default this cmdlets generates a password of length 16 with upper case, lower case, numeral and special characters.

    "newpwd" is the alias of this cmdlet.

.Parameter Length
    The length of the output. It must be at least 4 and at most 256. The default is 16.

.Parameter RNGImplementation
    The name of the implementation of cryptographically secure random number generation algorithm.

    "RNGAlgorithm" and "RNG" are the aliases of this parameter.

.Parameter NoUpperCaseCharacters
    Suppresses upper case characters from the output. These include ABCDEFGHIJKLMNOPQRSTUVWXYZ.

    "NoUC" is the alias of this switch.

.Parameter NoLowerCaseCharacters
    Suppresses lower case characters from the output. These include abcdefghijklmnopqrstuvwxyz.

    "NoLC" is the alias of this switch.

.Parameter NoNumeralCharacters
    Suppresses numeral characters from the output. These include 0123456789.

    "NoNum" is the alias of this switch.

.Parameter NoSpecialCharacters
    Suppresses special characters from the output. These include `~!@#$%^&*()_+-={}[]|\;':"<>?,./ and space.

    "NoSpecial" is the alias of this switch.

.Parameter AllowSimilarCharacters
    Allowes similar characters in the output. These include 1, l and I, 0 and O and `, ' and ".

.Parameter AllowSpace
    Allowes the space character in the output.

.Parameter UseSecureString
    Pipes out a System.Security.SecureString instead of string.

.Parameter Elder
    Forces the output to end with "+1s". If this switch is set, -UseSecureString will be cleared even if set explicitly. However, NONE of -NoLowerCaseCharacters, -NoNumeralCharacters and -NoSpecialCharacters are required to be cleared.

    "ls" is the alias of this switch. Therefore you get one second subtracted if you extend the elder's life for one second.

.Example
    New-Password -Length 20 -AllowSimilarCharacters

    This creates a 20-character long password possibly with similar characters.

    Possible output: "X9kw5Bc2~W^16EzuU]jJ"

.Example
    New-Password -NoSpecialCharacters -UseSecureString

    This creates a 16-character long password without special characters as a SecureString.

    Possible output: A System.Security.SecureString object.

.Example
    New-Password -Elder

    This creates a 16-character long password that ends with "+1s".

    Possible output: ">nvaM!$HAAr;v+1s"

.Link
    https://github.com/GeeLaw/PowerShellThingies/blob/master/modules/CommonUtilities/New-Password.md

#>

Function New-Password
{
    [CmdletBinding()]
    [Alias('newpwd')]
    Param
    (
        [Parameter(ValueFromPipeline = $true)]
        [ValidateRange(4, 256)]
        [int]$Length = 16,
        [Alias("RNGAlgorithm", "RNG")]
        [string]$RNGImplementation,
        [Alias("NoUC")]
        [switch]$NoUpperCaseCharacters,
        [Alias("NoLC")]
        [switch]$NoLowerCaseCharacters,
        [Alias("NoNum")]
        [switch]$NoNumeralCharacters,
        [Alias("NoSpecial")]
        [switch]$NoSpecialCharacters,
        [switch]$AllowSimilarCharacters,
        [switch]$AllowSpace,
        [switch]$UseSecureString,
        [Alias("ls", "o-o")]
        [switch]$Elder
    )
    Process
    {
        $local:uc = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
        $local:lc = 'abcdefghijkmnopqrstuvwxyz';
        $local:nu = '234567892345678923456789';
        $local:sp = '~!@#$%^&*()_+{}|[]\-=:;<>?,./';
        If ($AllowSimilarCharacters)
        {
            $uc += 'IO'; $lc += 'l';
            $nu += '010101'; $sp += "'" + '`"';
        }
        If ($AllowSpace)
        {
            $sp += ' ';
        }
        $local:lib = '';
        If (-not $NoUpperCaseCharacters)
        {
            $lib += $uc;
        }
        If (-not $NoLowerCaseCharacters)
        {
            $lib += $lc;
        }
        If (-not $NoNumeralCharacters)
        {
            $lib += $nu;
        }
        If (-not $NoSpecialCharacters)
        {
            $lib += $sp;
        }
        If ($lib.Length -eq 0)
        {
            Write-Error 'At least one category of characters must be allowed.';
            Return;
        }
        If ($Elder)
        {
            If ($UseSecureString)
            {
                Write-Warning '-UseSecureString is cleared by -Elder.';
            }
            $UseSecureString = $false;
            <# Sets these switches so that the algorithm no longer checks them.
             # But $lib already contains the specified characters, therefore
             # the generation rule is still correct.
             #>

            $NoLowerCaseCharacters = $true;
            $NoNumeralCharacters = $true;
            $NoSpecialCharacters = $true;
            $Length -= 3;
        }
        $local:rnd = $null;
        If ([string]::IsNullOrEmpty($RNGImplementation))
        {
            $rnd = New-Object -TypeName 'System.Security.Cryptography.RNGCryptoServiceProvider';
        }
        Else
        {
            $rnd = New-Object -TypeName 'System.Security.Cryptography.RNGCryptoServiceProvider' -ArgumentList $RNGImplementation;
        }
        $local:result = $null;
        $local:byteHolder = New-Object -TypeName 'byte[]' -ArgumentList @(1);
        If ($UseSecureString)
        {
            <# This instance will be disposed immediately in the first round of the loop. #>
            $result = New-Object -TypeName 'System.Security.SecureString';
        }
        $local:hasUC = $false; $local:hasLC = $false; $local:hasNU = $false; $local:hasSP = $false;
        $local:trimming = $false;
        $local:i = 0;
        Do
        {
            $hasUC = $false; $hasLC = $false; $hasNU = $false; $hasSP = $false;
            $trimming = $false;
            If ($UseSecureString)
            {
                $result.Dispose();
                $result = New-Object -TypeName 'System.Security.SecureString';
                For ($i = 0; $i -lt $Length; ++$i)
                {
                    Do
                    {
                        $rnd.GetBytes($byteHolder);
                    }
                    Until ([int]($byteHolder[0] / $lib.Length) -ne [int](256 / $lib.Length));
                    $result.AppendChar($lib[$byteHolder[0] % $lib.Length]);
                    If ($uc.Contains($lib[$byteHolder[0] % $lib.Length]))
                    {
                        $hasUC = $true;
                    }
                    If ($lc.Contains($lib[$byteHolder[0] % $lib.Length]))
                    {
                        $hasLC = $true;
                    }
                    If ($nu.Contains($lib[$byteHolder[0] % $lib.Length]))
                    {
                        $hasNU = $true;
                    }
                    If ($sp.Contains($lib[$byteHolder[0] % $lib.Length]))
                    {
                        $hasSP = $true;
                    }
                    if ($lib[$byteHolder[0] % $lib.Length] -eq ' '[0] -and ($i -eq 0 -or $i -eq ($Length - 1)))
                    {
                        $trimming = $true;
                    }
                }
                $result.MakeReadOnly();
            }
            Else
            {
                $result = '';
                For ($i = 0; $i -lt $Length; ++$i)
                {
                    Do
                    {
                        $rnd.GetBytes($byteHolder);
                    }
                    Until ([int]($byteHolder[0] / $lib.Length) -ne [int](256 / $lib.Length));
                    $result += $lib[$byteHolder[0] % $lib.Length];
                    If ($uc.Contains($lib[$byteHolder[0] % $lib.Length]))
                    {
                        $hasUC = $true;
                    }
                    If ($lc.Contains($lib[$byteHolder[0] % $lib.Length]))
                    {
                        $hasLC = $true;
                    }
                    If ($nu.Contains($lib[$byteHolder[0] % $lib.Length]))
                    {
                        $hasNU = $true;
                    }
                    If ($sp.Contains($lib[$byteHolder[0] % $lib.Length]))
                    {
                        $hasSP = $true;
                    }
                }
                If ($Elder)
                {
                    $result += '+1s';
                }
                If ($result[0] -eq ' '[0] -or $result[$result.Length - 1] -eq ' '[0])
                {
                    $trimming = $true;
                }
            }
            $byteHolder[0] = 0;
        }
        Until (-not $trimming -and ($NoUpperCaseCharacters -or $hasUC) -and ($NoLowerCaseCharacters -or $hasLC) -and ($NoNumeralCharacters -or $hasNU) -and ($NoSpecialCharacters -or $hasSP));
        $rnd.Dispose();
        Return $result;
    }
}


<#
.Synopsis
    Switches to elevated PowerShell or PowerShell run as another user.

.Description
    When called from an usual PowerShell prompt, it tries to start the elevated PowerShell. If it succeeds, the calling window is hidden. When the elevated PowerShell exits (by invoking exit or any other means), the calling window reappears, providing a seamless experience of elevation.

    The same rule applies for switching to another user.

.Link
    https://github.com/GeeLaw/PowerShellThingies/blob/master/modules/CommonUtilities/Switch-User.md

#>

Function Switch-User
{
    [CmdletBinding()]
    [Alias('su')]
    Param
    (
        [Parameter(ValueFromPipeline = $true)]
        [Alias('user', 'as', 'to')]
        [System.Management.Automation.PSCredential]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )
    Process
    {
        $local:ErrorActionPreference = 'Stop';
        If ($Host.Name -ne 'ConsoleHost')
        {
            Write-Error 'This cmdlet can only be invoked from PowerShell.';
            Return;
        }
        $local:IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator');
        $local:currentPathUnicodeBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes((Get-Location).Path));
        $local:suProcessInitCmd = '& { ';
        $suProcessInitCmd += 'Set-Location -Path ([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String(';
        $suProcessInitCmd += "'";
        $suProcessInitCmd += $currentPathUnicodeBase64;
        $suProcessInitCmd += "'";
        $suProcessInitCmd += '))); ';
        $suProcessInitCmd += '}';
        $local:suProcess = $null;
        $local:currentProcess = $null;
        $local:wasVisible = $true;
        If ($IsAdmin -and [object]::ReferenceEquals($Credential, [System.Management.Automation.PSCredential]::Empty))
        {
            $Credential = Get-Credential -Message 'Please specify the credential to run PowerShell.';
            If ($Credential -eq $null)
            {
                Write-Error 'Action cancelled by user.' -Category OperationStopped;
                Return;
            }
        }
        If ([object]::ReferenceEquals($Credential, $null) -or [object]::ReferenceEquals($Credential, [System.Management.Automation.PSCredential]::Empty))
        {
            $suProcess = Start-Process -PassThru -FilePath 'PowerShell.exe' -Verb 'runas' `
                -ArgumentList @('-NoExit', '-ExecutionPolicy', (Get-ExecutionPolicy).ToString(), '-Command', $suProcessInitCmd);
            If ($suProcess -eq $null)
            {
                Return;
            }
            Write-Verbose "Another process started on $($suProcess.StartTime), ProcessId = $($suProcess.Id).";
            $currentProcess = Get-Process -Id $pid;
            $wasVisible = [__3b043047842e4cfa94dbcb39a5ccf3e5.SwitchUser]::ShowWindow($currentProcess.MainWindowHandle, 0);
            $suProcess.WaitForExit();
            Write-Verbose "The process exited on $($suProcess.ExitTime).";
            If ($suProcess.ExitCode -eq 0)
            {
                Write-Verbose 'The process exited with code 0.';
            }
            Else
            {
                Write-Warning "The process exited with code $($suProcess.ExitCode).";
            }
        }
        Else
        {
            $suProcess = Start-Process -PassThru -FilePath 'PowerShell.exe' `
                -ArgumentList @('-NoExit', '-ExecutionPolicy', (Get-ExecutionPolicy).ToString(), '-Command', $suProcessInitCmd) `
                -Credential $Credential;
            If ($suProcess -eq $null)
            {
                Return;
            }
            Write-Verbose "Another process started on $($suProcess.StartTime), ProcessId = $($suProcess.Id).";
            $currentProcess = Get-Process -Id $pid;
            $wasVisible = [__3b043047842e4cfa94dbcb39a5ccf3e5.SwitchUser]::ShowWindow($currentProcess.MainWindowHandle, 0);
            $suProcess.WaitForExit();
            Write-Verbose 'Another process has exited. However, running as another user does not give ExitTime or ExitCode.';
        }
        $suProcess.Dispose();
        If ($wasVisible)
        {
            [__3b043047842e4cfa94dbcb39a5ccf3e5.SwitchUser]::ShowWindow($currentProcess.MainWindowHandle, 5) | Out-Null;
            [__3b043047842e4cfa94dbcb39a5ccf3e5.SwitchUser]::SwitchToThisWindow($currentProcess.MainWindowHandle, $True);
            [__3b043047842e4cfa94dbcb39a5ccf3e5.SwitchUser]::BringWindowToTop($currentProcess.MainWindowHandle) | Out-Null;
            If (-not [__3b043047842e4cfa94dbcb39a5ccf3e5.SwitchUser]::SetForegroundWindow($currentProcess.MainWindowHandle))
            {
                $currentProcess.Dispose();
                Write-Error 'Failed to recover the hidden window.';
                Return;
            }
        }
        $currentProcess.Dispose();
        Return;
    }
}

<#
.Synopsis
    A shortcut for Set-AuthenticodeSignature.

.Description
    Use this cmdlet to sign your code (PowerShell scripts, modules, manifests
    and so on). When no certificate is supplied, the cmdlet tries to use your
    code-signing certificate(s).

    "sign" is the alias of this cmdlet.

.Parameter Scripts
    The path of the scripts to sign. This parameter is mandatory. It receives
    value from the pipeline by value and by property (aliased FullName so that
    you can pipe FileInfo object generated by Get-ChildItem).

.Parameter Certificate
    The certificate to use.

    If unspecified, the cmdlet enumerates all your personal code-signing
    certificates. If there is only one such certificate, the scripts are signed
    with this certificate; otherwise you interactively choose one certificate.
    If no such certificate is found, the cmdlet fails.

.Example
    Sign-Scripts -Scripts $profile

    This line signs your profile script with your personal code-signing
    certificate(s).

.Example
    Get-ChildItem -File -Recurse | Sign-Scripts

    This line signs all the files (including those in subfolders) with your
    personal code-signing certificate(s). This line works best if you have one
    and only one such certificate in your personal store.

.Link
    https://github.com/GeeLaw/PowerShellThingies/blob/master/modules/CommonUtilities/Sign-Scripts.md

#>

Function Sign-Scripts
{
    [CmdletBinding()]
    [Alias('sign')]
    Param
    (
        [Parameter(Mandatory = $true, ValueFromRemainingArguments = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName')]
        [string[]]$Scripts,
        [Parameter(Mandatory = $false)]
        [Alias('with')]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate
    )
    Begin
    {
        If ($Certificate -eq $null)
        {
            $local:certs = Get-ChildItem -LiteralPath 'Cert:\CurrentUser\My' -CodeSigningCert;
            If ($certs.Count -eq 1)
            {
                Write-Verbose 'Signing code with the following certificate:';
                $certs[0] | Format-List | Out-String | Write-Verbose;
                $Certificate = $certs[0];
            }
            ElseIf ($certs.Count -gt 1)
            {
                Write-Host 'Multiple certificates are available in your personal storage.';
                Write-Host;
                $local:i = 0;
                $certs | ForEach-Object {
                        Write-Host "Certificate[$i]:";
                        $_ | Format-List | Out-String | Write-Host;
                        $i = $i + 1;
                    };
                $local:choice = Read-Host -Prompt 'Please specify the certificate';
                $local:choiceInt = 0;
                If ([int]::TryParse($choice, [ref] $choiceInt))
                {
                    If ($choiceInt -ge 0 -and $choiceInt -lt $certs.Count)
                    {
                        $Certificate = $certs;
                    }
                    Else
                    {
                        Throw [IndexOutOfRangeException];
                    }
                }
                Else
                {
                    Throw [FormatException] 'You must specifiy an index.';
                }
            }
            Else
            {
                throw [Exception] 'You do not have a certifcate in your personal storage. Please specify the certificate in the command.';
            }
        }
        If ($Certificate -eq $null)
        {
            Break;
        }
    }
    Process
    {
        If ($Certificate -ne $null)
        {
            Set-AuthenticodeSignature -FilePath $Scripts -Certificate $Certificate;
        }
    }
}

<#
.SYNOPSIS
    Restarts the host.

.LINK
    https://github.com/GeeLaw/PowerShellThingies/tree/master/modules/CommonUtilities

#>

Function Restart-Host
{
    [CmdletBinding()]
    [Alias('restart')]
    Param()
    Process
    {
        Start-Process -FilePath (Get-Process -Id $pid).Path;
        Exit;
    }
}

<#
.SYNOPSIS
    Sets a fast credential.

.DESCRIPTION
    This advanced function stores a credential with default
    encryption method under %LOCALAPPDATA%\FastCredentials.

    It supports user names in the form of USERNAME or
    DOMAIN\USERNAME, where DOMAIN and USERNAME must consist
    of only valid file name characters, and not end with a
    full stop.

    Under proper security configuration (up-to-date Windows,
    BitLocker, proper permission on %USERPROFILE% and strong
    password), the stored credential is only accessible from
    the current user. See the online help for canonical usage.

.PARAMETER Credential
    One single credential to be stored. The value can be piped
    from another command like Get-Credential.

.EXAMPLE
    Get-Credential | Set-FastCredential

    This example reads a credential from GUI and saves it.

.LINK
    https://github.com/GeeLaw/PowerShellThingies/blob/master/modules/CommonUtilities/FastCredential.md
#>

Function Set-FastCredential
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSCredential]$Credential
    )
    Process
    {
        Try
        {
            $local:ssString = ConvertFrom-SecureString -SecureString $Credential.Password;
            $local:localAppData = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::LocalApplicationData);
            If (-not (Test-Path -LiteralPath ([System.IO.Path]::Combine($local:localAppData, 'FastCredentials'))))
            {
                New-Item -Path $local:localAppData -Name 'FastCredentials' -ItemType 'Directory' | Out-Null;
            }
            Push-Location -LiteralPath ([System.IO.Path]::Combine($local:localAppData, 'FastCredentials'));
            Try
            {
                $Credential.UserName.Split('\') | Select-Object -SkipLast 1 | ForEach-Object {
                    If (-not (Test-Path -LiteralPath $_))
                    {
                        New-Item -Name $_ -ItemType 'Directory' | Out-Null;
                    }
                    Set-Location -LiteralPath $_;
                };
            }
            Catch
            {
                Throw;
            }
            Finally
            {
                Pop-Location;
            }
            $local:fileName = [System.IO.Path]::Combine($local:localAppData, 'FastCredentials', $Credential.UserName + '.fastcred');
            [System.IO.File]::WriteAllText($fileName, $ssString);
        }
        Catch
        {
            Throw;
        }
    }
}

<#
.SYNOPSIS
    Retrieves a fast credential.

.DESCRIPTION
    This advanced function reads a credential with default
    encryption method under %LOCALAPPDATA%\FastCredentials.

    It supports user names in the form of USERNAME or
    DOMAIN\USERNAME, where DOMAIN and USERNAME must consist
    of only valid file name characters, and not end with a
    full stop.

.PARAMETER UserName
    User name of the credential to be retrieved.

.EXAMPLE
    Start-Process -FilePath 'powershell' `
        -Credential (Get-FastCredential 'AnotherUser')

    Starts PowerShell as AnotherUser.

.LINK
    https://github.com/GeeLaw/PowerShellThingies/blob/master/modules/CommonUtilities/FastCredential.md
#>

Function Get-FastCredential
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$UserName
    )
    Process
    {
        Try
        {
            $local:localAppData = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::LocalApplicationData);
            $local:fileName = [System.IO.Path]::Combine($local:localAppData, 'FastCredentials', $UserName + '.fastcred');
            $local:ssString = [System.IO.File]::ReadAllText($fileName);
            $local:ssString = ConvertTo-SecureString -String $local:ssString;
            $local:cred = [PSCredential]::new($UserName, $local:ssString);
            Return $local:cred;
        }
        Catch
        {
            Throw;
        }
    }
}

<#
.SYNOPSIS
    Removes a fast credential.

.DESCRIPTION
    This advanced function removes a credential under
    %LOCALAPPDATA%\FastCredentials.

    It supports user names in the form of USERNAME or
    DOMAIN\USERNAME, where DOMAIN and USERNAME must consist
    of only valid file name characters, and not end with a
    full stop.

.PARAMETER UserName
    User name of the credential to be removed.

.LINK
    https://github.com/GeeLaw/PowerShellThingies/blob/master/modules/CommonUtilities/FastCredential.md
#>

Function Remove-FastCredential
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$UserName
    )
    Process
    {
        Try
        {
            $local:localAppData = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::LocalApplicationData);
            $local:fileName = [System.IO.Path]::Combine($local:localAppData, 'FastCredentials', $UserName + '.fastcred');
            Remove-Item -LiteralPath $local:fileName -Recurse:$false;
        }
        Catch
        {
            Throw;
        }
    }
}

[ScriptBlock]$OutTextEditorPreference = {
    If ([System.Linq.Enumerable]::Any($_, [char].GetMethod('IsWhiteSpace', [type[]]@([char])).CreateDelegate([System.Func[char, bool]])))
    {
        $_ = '"' + $_ + '"';
    }
    Start-Process -FilePath 'code' -ArgumentList @($_) -NoNewWindow;
};

<#
.SYNOPSIS
    Sends output to a temporary text file and opens a text editor.

.DESCRIPTION
    The Out-TextEditor advanced function sends output to a temporary text file and opens a text editor (Visual Studio Code by default). Using a text editor allows you to do advanced searching and editing to the textual representation of the input object(s).

    It is important to note that this advanced function is intended to be used interactively or at least in a GUI fashion. The format of output (especially the header style) is subject to change and you should NOT depend on the specific styling found in any version of the advanced function.

.PARAMETER InputObject
    Specifies the objects to be written to the file. Accepts values from the pipeline.

.PARAMETER Encoding
    Specifies the type of character encoding used in the file. This parameter accepts the same set of values accepted by Out-File. If unspecified, the encoding is up to Out-File.

.PARAMETER Width
    Specifies the number of characters in each line of output. Any additional characters are truncated, not wrapped. If you omit this parameter, the width is determined by Out-File.

.PARAMETER EditorCommand
    Specifies how to open the text editor with a script block. The script block should accept pipeline item ($_ or $PSItem) that is supposed to be the full name of the temporary file. If you omit this parameter or it is $null, the value comes from $OutTextEditorPreference. The default value opens Visual Studio Code by running code <filename>. The name is quoted if it contains a white space.

.PARAMETER NoHeader
    Suppresses the header. By default, the advanced function prints the line of command ($MyInvocation.Line) to the first line of the file, and a fence in the following line. If this switch is on, the header is suppressed.

    You must NOT supply both NoHeader switch and Header parameter.

.PARAMETER Header
    Specifies a custom header. Supplying this parameter replaces the default header.

    You must NOT supply both NoHeader switch and Header parameter.

.PARAMETER PassThru
    Pipes the input downstream. By default, input objects are eaten and not piped.

.LINK
    https://github.com/GeeLaw/PowerShellThingies/blob/master/modules/CommonUtilities/Out-TextEditor.md
#>

Function Out-TextEditor
{
    [CmdletBinding(DefaultParameterSetName = 'DefaultHeader', PositionalBinding = $False)]
    [Alias('ovsc')]
    Param
    (
        [Parameter(Mandatory = $False, ValueFromPipeline = $True, ParameterSetName = 'DefaultHeader')]
        [Parameter(Mandatory = $False, ValueFromPipeline = $True, ParameterSetName = 'CustomHeader')]
        [PSObject]$InputObject,
        [Parameter(Mandatory = $False, ParameterSetName = 'DefaultHeader')]
        [Parameter(Mandatory = $False, ParameterSetName = 'CustomHeader')]
        [ValidateSet('Unknown', 'String', 'Unicode', 'BigEndianUnicode',
            'UTF8', 'UTF7', 'UTF32', 'ASCII', 'Default', 'OEM')]
        [string]$Encoding,
        [Parameter(Mandatory = $False, ParameterSetName = 'DefaultHeader')]
        [Parameter(Mandatory = $False, ParameterSetName = 'CustomHeader')]
        [ValidateRange(2, [int]::MaxValue)]
        [int]$Width,
        [Parameter(Mandatory = $False, ParameterSetName = 'DefaultHeader')]
        [Parameter(Mandatory = $False, ParameterSetName = 'CustomHeader')]
        [ScriptBlock]$EditorCommand,
        [Parameter(Mandatory = $False, ParameterSetName = 'DefaultHeader')]
        [switch]$NoHeader,
        [Parameter(Mandatory = $True, ParameterSetName = 'CustomHeader')]
        [switch]$Header,
        [Parameter(Mandatory = $False, ParameterSetName = 'DefaultHeader')]
        [Parameter(Mandatory = $False, ParameterSetName = 'CustomHeader')]
        [switch]$PassThru
    )
    Begin
    {
        [string]$TempFileName = [System.IO.Path]::Combine(
            [System.IO.Path]::GetTempPath(),
            [System.Guid]::NewGuid().ToString('n') + '.txt');
        [System.Collections.Generic.List[System.Management.Automation.PSObject]]$InputObjects = [System.Collections.Generic.List[System.Management.Automation.PSObject]]::new();
        New-Item -Path $TempFileName -ItemType 'File' | Out-Null;
        [HashTable]$OutFileArgs = @{ 'FilePath' = $TempFileName; 'Append' = $True };
        <# Value is empty string if not supplied. #>
        If (-not [string]::IsNullOrWhiteSpace($Encoding))
        {
            $OutFileArgs['Encoding'] = $Encoding;
        }
        <# Value is 0 if not supplied. #>
        If ($Width -ge 2)
        {
            $OutFileArgs['Width'] = $Width;
        }
        Else
        {
            $Width = 80 - 1;
            Try
            {
                $Width = [System.Math]::Max(2, $Host.UI.RawUI.WindowSize.Width - 1);
            }
            Catch
            {
            }
        }
        If ($EditorCommand -eq $null)
        {
            $EditorCommand = $OutTextEditorPreference;
        }
        If ($EditorCommand -eq $null)
        {
            $local:noEditorError = [System.Management.Automation.ErrorRecord]::new(
                [System.ArgumentNullException]::new('EditorCommand', 'Both EditorCommand parameter and $OutTextEditorPreference variable are null.'),
                'NoEditorCommand',
                [System.Management.Automation.ErrorCategory]::InvalidArgument,
                $null
            );
            $PSCmdlet.WriteError($noEditorError);
            Return;
        }
        If ($PSCmdlet.ParameterSetName -eq 'DefaultHeader')
        {
            If (-not $NoHeader)
            {
                @($MyInvocation.Line, ('-' * $Width)) | Out-File @OutFileArgs;
            }
        }
        Else
        {
            @($Header, ('-' * $Width)) | Out-File @OutFileArgs;
        }
    }
    Process
    {
        If ($EditorCommand -ne $null)
        {
            If ($InputObject -is [object[]])
            {
                $InputObjects.AddRange($InputObject);
            }
            Else
            {
                $InputObjects.Add($InputObject);
            }
        }
        If ($PassThru)
        {
            $InputObject;
        }
    }
    End
    {
        If ($EditorCommand -ne $null)
        {
            $OutFileArgs['InputObject'] = $InputObjects;
            Out-File @OutFileArgs;
            $TempFileName | ForEach-Object $EditorCommand | Out-Null;
        }
    }
}

Export-ModuleMember -Function @('New-Password', 'Switch-User', 'Sign-Scripts', 'Restart-Host', 'Set-FastCredential', 'Get-FastCredential', 'Remove-FastCredential', 'Out-TextEditor') -Alias @('newpwd', 'su', 'sign', 'restart', 'ovsc') -Cmdlet @() -Variable @('OutTextEditorPreference');