ExecutechUtilities.psm1

<#
.SYNOPSIS
    PowerShell Module for Shared and Frequently Used Utility Functions
 
.DESCRIPTION
    Collection of frequently used helper functions. Required by other Executech scripts and modules.
 
.NOTES
    Version: 1.0.21
    Author: Josiah McCall
    Website: executech.com
    Creation Date: 09/12/2023
    Purpose/Change: Initial script development
 
    Update Date: 2024.12.11
    Purpose/Change: Revised the Get-ExceptionReport function to address issues found in testing.
                    Updated the New-TimeStamp function to add a -fileName switch to use file-appropriate characters.
 
    PREVIOUS CHANGELOG:
 
    2024.11.12
        Adjusted the Initialize-Module function to resolve errors related to pre-release modules and versions with '-betaXYZ' on the end.
        Added a modified version of the Get-WiFiProfile function from the WiFiProfileManagement module (source: https://github.com/jcwalker/WiFiProfileManagement)
            - Cleaned up function code, replaced inefficient array structures with collections, and bundled all necessary Private functions inside the main function
    2024.11.04
        Added new function Get-ExceptionReport
    2024.10.31
        Added new function 'ConvertFrom-DistinguishedName'.
        Updated the exported functions and aliases in the PSD1 file.
    2024.10.14
        Updated Get-MySQLEncodedString function to fix PS 7.x error.
        Updated Get-CleanFileName/Base64/Compress string functions to add pipeline input.
    2024.10.03
        Added new function: Get-CleanFileName
    2024.09.26
        Added option to New-LogEntry function to suppress the timestamp.
        Added additional logic to the Get-LocalTime function to simplify the calls and support input string already in UTC format.
    2024.09.17
        Add new functions for the IGatherDataSet and IGatherEntry classes; when executing via Import-Module, the classes are not accessible directly.
    2024.09.13
        Removed all references to the LabTechCommon DLL files.
        Get/Set CompressedString, AutomateEncryptedString, CleanString, and MySQLEncodedString functions rewritten in native PowerShell code.
    2024.08.16
        Added Get-MySQLEncodedString function.
    03/05/2024
        Added function for converting datetime to MySQL string, separate from Get-LocalTime which assumes UTC value.
    02/28/2024
        Added new CWADataSet class code and functions to initialize.
    02/06/2024
        Added aliases for several commands.
    01/25/2024
        Rebuilt the Initialize-Module functions to make them cleaner and more resilient.
    01/10/2024
        Added IGatherDataSet class.
    11/14/2023
        Added regions for standard verbs in use (Copy, Get, Set, etc.).
        Added Copy-ACL and CopyFileTimestamps functions.
#>


$ModuleVersion = "1.0.21"
$ModuleGuid = 'dd8bdf00-aab6-4efc-971a-c0bbe32d1c5c';

Set-Variable -Name HeaderLine -Value '==================================================================================================' -Visibility Public -Scope Local -Option ReadOnly -Force;
Set-Variable -Name FooterLine -Value '--------------------------------------------------------------------------------------------------' -Visibility Public -Scope Local -Option ReadOnly -Force;

Set-Variable -Name Symbols -Visibility Public -Scope Local -Option ReadOnly -Force -Value $([PSCustomObject]@{
        ArrowDown     = [char]11107
        ArrowDualH    = [char]11136
        ArrowDualV    = [char]11137
        ArrowLeft     = [char]11104
        ArrowRight    = [char]11106
        ArrowUp       = [char]11105
        ArrowBiDirH   = [char]11108
        ArrowBiDirV   = [char]11109
        Cancel        = [char]9747
        CancelCircle  = [char]11198
        CancelSquare  = [char]10062
        CheckMark     = [char]10003
        CheckSquare   = [char]9989
        Crossbones    = [char]9760
        DblArrowDown  = [char]11143
        DblArrowLeft  = [char]11140
        DblArrowRight = [char]11142
        DblArrowUp    = [char]11141
        DottedX       = [char]8251
        FingerPointL  = [char]9754
        FingerPointR  = [char]9755
        Hourglass     = [char]8987
        Infinity      = [char]8734
        Information   = [char]8505
        Keyboard      = [char]9000
        Recycle       = [char]9851
        Refresh       = [char]11119
        Stop          = [char]9940
        Warning       = [char]9888
        Watch         = [char]8986
    })

# This is required for the Get-Automate[Encrypted/Decrypted]String and Get-[Un]ComporessedString functions.

function Import-LabTechCommon {

    Write-Verbose "NOTICE: The Import-LabTechCommon function has been deprecated and no longer does anything.";
    Write-Verbose "NOTICE: All functions that previously relied upon the external DLL files have been rewritten in native PS code.";
}

# These two are required for the New-MessageBox function.

function Import-Presentation {

    if ($null -eq ([appdomain]::currentdomain.getassemblies() | Where-Object { $_ -match "PresentationCore" })) {
        try {
            Add-Type -AssemblyName PresentationCore -ErrorAction Stop;
        } catch {
            Write-Warning -Message "WARNING: Failed to import PresentationCore assembly.";
            Write-Error $_.Exception.Message;
        }
    }

    if ($null -eq ([appdomain]::currentdomain.getassemblies() | Where-Object { $_ -match "PresentationFramework" })) {
        try {
            Add-Type -AssemblyName PresentationFramework -ErrorAction Stop;
        } catch {
            Write-Warning -Message "WARNING: Failed to import PresentationFramework assembly.";
            Write-Error $_.Exception.Message;
        }
    }
}

# For the New-InputBox function.

function Import-VisualBasic {

    if ($null -eq ([appdomain]::currentdomain.getassemblies() | Where-Object { $_ -match "Microsoft.VisualBasic" })) {
        try {
            Add-Type -AssemblyName Microsoft.VisualBasic -ErrorAction Stop;
        } catch {
            Write-Warning -Message "WARNING: Failed to import Microsoft.VisualBasic assembly.";
            Write-Error $_.Exception.Message;
        }
    }
}

#region Custom Classes

#public
class IGatherDataSet {
    [int] $ComputerID
    [string] $Category
    [System.Collections.Generic.List[IGatherEntry]] $Data
    [string] $Log
    [string] $Errors
    [bool] $Process

    IGatherDataSet([int] $computerId, [string] $category, [System.Collections.Generic.List[IGatherEntry]] $gatherEntries, [string] $logs, [string] $errors, [bool] $shouldprocess = $false) {
        $this.ComputerID = $computerId;
        $this.Category = $category;
        $this.Data = $gatherEntries;
        $this.Log = $logs;
        $this.Errors = $errors;
        $this.Process = $shouldprocess;
    }

    IGatherDataSet([int] $computerId, [string] $category) {
        $this.ComputerID = $computerId;
        $this.Category = $category;
        $this.Data = [System.Collections.Generic.List[IGatherEntry]]::new();
    }

    IGatherDataSet([System.Management.Automation.PSObject] $psobject) {
        foreach ($prop in $this.GetType().GetProperties()) {
            $prop.SetValue($this, $(if ($null -eq $psobject[$prop.Name]) { "" } else { $psobject[$prop.Name] }));
        }
    }

    IGatherDataSet() {
        $this.Data = [System.Collections.Generic.List[IGatherEntry]]::new();
    }
}

#public
class IGatherEntry {

    [string] $Table
    [string] $InsertString
    [string] $DeleteFilter
    [string] $DuplicateString

    IGatherEntry([string] $table, [string] $insert, [string] $delete, [string] $dupe) {
        $this.Table = $table;
        $this.InsertString = $insert;
        $this.DeleteFilter = $delete;
        $this.DuplicateString = $dupe;
    }

    IGatherEntry([string] $table, [string] $insert, [string] $delete) {
        $this.Table = $table;
        $this.InsertString = $insert;
        $this.DeleteFilter = $delete;
        $this.DuplicateString = "NONE";
    }

    IGatherEntry([System.Management.Automation.PSObject] $psobject) {
        foreach ($prop in $this.GetType().GetProperties()) {
            $prop.SetValue($this, $(if ($null -eq $psobject[$prop.Name]) { "" } else { $psobject[$prop.Name] }));
        }
    }

    IGatherEntry() {
    }
}


$IGatherDataSetCode = @"
[System.ComponentModel.TypeConverter(typeof(IGatherDataSet))]
public class IGatherDataSet
{
    public int ComputerID { get; set; }
    public string Category { get; set; }
    public System.Collections.Generic.List<IGatherEntry> Data { get; set; }
    public string Log { get; set; }
    public string Errors { get; set; }
    public bool Process { get; set; }
 
    public IGatherDataSet(int computerId, string category, System.Collections.Generic.List<IGatherEntry> gatherEntries, string logs, string errors, bool shouldprocess = false)
    {
        ComputerID = computerId;
        Category = category;
        Data = gatherEntries;
        Log = logs;
        Errors = errors;
        Process = shouldprocess;
    }
 
    public IGatherDataSet(System.Management.Automation.PSObject psobject)
    {
        foreach (var prop in this.GetType().GetProperties())
        {
            prop.SetValue(this, (psobject.Members[prop.Name] == null ? "" : psobject.Members[prop.Name].Value));
        }
    }
 
    public IGatherDataSet(int computerId, string category)
    {
        ComputerID = computerId;
        Category = category;
        Data = new System.Collections.Generic.List<IGatherEntry> { };
    }
 
    public IGatherDataSet()
    {
        Data = new System.Collections.Generic.List<IGatherEntry> { };
    }
}
 
[System.ComponentModel.TypeConverter(typeof(IGatherEntry))]
public class IGatherEntry
{
    public string Table { get; set; }
    public string InsertString { get; set; }
    public string DeleteFilter { get; set; }
    public string DuplicateString { get; set; }
 
    public IGatherEntry(string table, string insert, string delete)
    {
        Table = table;
        InsertString = insert;
        DeleteFilter = delete;
        DuplicateString = "NONE";
    }
 
    public IGatherEntry(string table, string insert, string delete, string dupe)
    {
        Table = table;
        InsertString = insert;
        DeleteFilter = delete;
        DuplicateString = dupe;
    }
 
    public IGatherEntry(System.Management.Automation.PSObject psobject)
    {
        foreach (var prop in this.GetType().GetProperties())
        {
            prop.SetValue(this, (psobject.Members[prop.Name] == null ? "" : psobject.Members[prop.Name].Value));
        }
    }
 
    public IGatherEntry()
    {
    }
}
"@


Set-Variable -Name IGatherDataSet -Value $IGatherDataSetCode -Visibility Public -Scope Local -Option ReadOnly -Force;

function Initialize-IGatherDataSet() {

    $ErrorActionPreference = 'SilentlyContinue';

    if (-not ([System.Management.Automation.PSTypeName]'IGatherDataSet').Type) {

        try {

            Add-Type -TypeDefinition $IGatherDataSet -Language CSharp -ErrorAction Stop;

        } catch {
            throw $_.Exception;
        }
    }
}

$CWADataSetCode = @"
public class CWADataSet
{
    public string Task { get; set; }
    public System.Collections.Generic.List<CWADataEntry> DataQueries { get; set; }
    public string Logs { get; set; }
    public string Errors { get; set; }
    public bool Process { get; set; }
 
    public CWADataSet(string task, System.Collections.Generic.List<CWADataEntry> queries, string logs, string errors, bool process)
    {
        Task = task;
        Process = process;
        DataQueries = queries;
        Logs = logs;
        Errors = errors;
    }
 
    public CWADataSet(System.Management.Automation.PSObject psobject)
    {
        foreach (var prop in this.GetType().GetProperties())
        {
            prop.SetValue(this, (psobject.Members[prop.Name] == null ? "" : psobject.Members[prop.Name].Value));
        }
    }
 
    public CWADataSet(string task)
    {
        Task = task;
        DataQueries = new System.Collections.Generic.List<CWADataEntry> { };
    }
 
    public CWADataSet()
    {
        DataQueries = new System.Collections.Generic.List<CWADataEntry> { };
    }
}
 
public class CWADataEntry
{
    public string Table { get; set; }
    public string Insert { get; set; }
    public string Delete { get; set; }
 
    public CWADataEntry(string table, string insert, string delete)
    {
        Table = table;
        Insert = insert;
        Delete = delete;
    }
 
    public CWADataEntry(System.Management.Automation.PSObject psobject)
    {
        foreach (var prop in this.GetType().GetProperties())
        {
            prop.SetValue(this, (psobject.Members[prop.Name] == null ? "" : psobject.Members[prop.Name].Value));
        }
    }
 
    public CWADataEntry(string table)
    {
        Table = table;
        Insert = "NONE";
        Delete = "NONE";
    }
 
    public CWADataEntry()
    {
 
    }
}
"@


Set-Variable -Name CWADataSet -Value $CWADataSetCode -Visibility Public -Scope Local -Option ReadOnly -Force;

function Initialize-CWADataSet() {

    $ErrorActionPreference = 'SilentlyContinue';

    if (-not ([System.Management.Automation.PSTypeName]'CWADataSet').Type) {

        try {

            Add-Type -TypeDefinition $CWADataSet -Language CSharp;

        } catch {
            throw $_.Exception;
        }
    }
}

#endregion Custom Classes

#region Copy

function Copy-ACL() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullorEmpty()]
        [String]$sourcePath,
        [Parameter(Position = 1, Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$destinationPath
    )

    try {

        $SourceACL = Get-Acl -Path $sourcePath;
        $DestinationACL = Get-Acl -Path $destinationPath;

        $Differences = Compare-Object -ReferenceObject $SourceACL -DifferenceObject $DestinationACL;

        if (-not($null -eq $Differences)) {
            Set-Acl -Path $destinationPath -AclObject $SourceACL;
            return "Copied ACL from $($sourcePath) to $($destinationPath)";
        } else {
            return "$($destinationPath) ACL matches $($sourcePath)";
        }

    } catch {
        return "ERROR: Failed to copy ACL from $($sourcePath) to $($destinationPath): $($_.Exception.Message)";
    }
}

function Copy-FileTimestamps() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullorEmpty()]
        [System.IO.FileInfo]$sourceFile,
        [Parameter(Position = 1, Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$targetFilePath
    )

    try {

        $newFile = Get-Item -Path $targetFilePath;

        if (-not($null -eq $sourceFile.LastAccessTime)) {
            $newFile.LastAccessTime = $sourceFile.LastAccessTime;
        }

        if (-not($null -eq $sourceFile.LastAccessTime)) {
            $newFile.LastAccessTime = $sourceFile.LastAccessTime;
        }

        return "Copied file timestamps from $($sourceFile.FullName) to $($newFile.FullName)";

    } catch {
        return "ERROR: Failed to copy file timestamps from $($sourceFile.FullName) to $($targetFilePath): $($_.Exception.Message)";
    }
}

#endregion Copy

#region Convert

function ConvertTo-Hashtable {
    [cmdletbinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Position = 0, Mandatory, HelpMessage = "Please specify an object", ValueFromPipeline)]
        [ValidateNotNullOrEmpty()][object]$InputObject,
        [switch]$NoEmpty,
        [string[]]$Exclude,
        [switch]$Alphabetical,
        [Parameter(HelpMessage = "Create an ordered hashtable instead of a plain hashtable.")]
        [switch]$Ordered
    )

    Process {

        # Get type using the [Type] class because deserialized objects won't have a GetType() method which is what I would normally use.
        $TypeName = [system.type]::GetTypeArray($InputObject).name
        Write-Verbose "Converting an object of type $TypeName"

        #get property names using Get-Member
        $PropertyNames = $InputObject | Get-Member -MemberType properties | Select-Object -ExpandProperty name

        if ($Alphabetical) {
            Write-Verbose "Sort property PropertyNames alphabetically"
            $PropertyNames = $PropertyNames | Sort-Object
        }

        #define an empty hash table
        if ($Ordered) {
            Write-Verbose "Creating an ordered hashtable"
            $hash = [ordered]@{ }
        } else {
            $hash = @{ }
        }

        #go through the list of PropertyNames and add each property and value to the hash table
        $PropertyNames | ForEach-Object {

            if ($Exclude -NotContains $_) {

                if ($NoEmpty -AND -Not ($InputObject.$_)) {
                    Write-Verbose "Skipping $_ as empty"
                } else {

                    Write-Verbose "Adding property $_"
                    $hash.Add($_, $InputObject.$_)
                }
            } else {
                Write-Verbose "Excluding $_"
            }
        }

        Write-Verbose "Writing the result to the pipeline"
        return $hash;
    }
}

function ConvertFrom-DistinguishedName {
    param (
        [Parameter(Mandatory, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
        [ValidateNotNullOrEmpty()]
        [alias('dn')]
        [string]$distinguishedName
    )

    process {

        $d = [System.Text.StringBuilder]::new();
        $p = [System.Text.StringBuilder]::new();

        $distinguishedName -split '(?<!\\),' | ForEach-Object {

            if ($_ -match '^DC=') {
                [void]$d.Append($_.Substring(3)).Append('.');
            } else {
                [void]$p.Append($('{0}\{1}' -f $_.Substring(3), $p.ToString()));
            }
        }

        return $($('{0}\{1}' -f $d.ToString().Trim('.'), ($p.ToString().TrimEnd('\') -replace '\\,', ',')));
    }

} Set-Alias -Name 'ConvertFrom-DN' -Value ConvertFrom-DistinguishedName -Option ReadOnly -Scope Global -Force;

#endregion Convert

#region Find

function Find-ReplaceStringInFile {
    [OutputType()]
    param (
        [Parameter(Position = 0, Mandatory)]
        [ValidateScript({ if (-Not (($_ | Test-Path -PathType Leaf) -or ( Test-Path -LiteralPath $_ ))) { throw "The Path argument must be a file. Folder paths are not allowed." } return $true })]
        [string]$Path,
        [Parameter(Position = 1, Mandatory)]
        [string]$Find,
        [Parameter(Position = 2, Mandatory)]
        [string]$Replace,
        [Parameter(Position = 3, Mandatory = $false)]
        [bool]$Backup = $true
    )

    begin {

        $Search = [regex]::Escape($Find);
        Write-Verbose "Find string: $Search";
    }

    process {

        try {

            if (Select-String -LiteralPath $Path -Pattern $Search) {

                Write-Verbose "Replacing $Find with $Replace in file $Path";

                $NewContent = [System.IO.File]::ReadAllText($Path).Replace($Find, $Replace);

                if (-not([string]::IsNullOrEmpty($NewContent))) {

                    if ($Backup) {

                        Write-Verbose "Copying $Path to $Path.bak";
                        Move-Item -LiteralPath $Path -Destination "$Path.bak" -Force;
                    }

                    Write-Verbose "Saving changes to $Path";
                    [System.IO.File]::WriteAllText($Path, $NewContent);

                    return "SUCCESS: $Path updated.";

                } else {
                    return "ERROR: Failed to read contents of $Path or file is empty."
                }

            } else {
                return "Skipped: $Find was not found in $($Path)."
            }

        } catch {
            return "ERROR: $($_.Exception.Message)";
        }
    }
}

#endregion Find

#region Get

function Get-SelfSignedAppCertificate() {
    Param(

        [Parameter(Mandatory = $true)]
        [string]$CommonName,

        [Parameter(Mandatory = $true)]
        [DateTime]$StartDate,

        [Parameter(Mandatory = $true)]
        [DateTime]$EndDate,

        [Parameter(Mandatory = $false, HelpMessage = 'Will overwrite existing certificates')]
        [Switch]$Force,

        [Parameter(Mandatory = $false)]
        [SecureString]$Password
    )

    # DO NOT MODIFY BELOW

    function Remove-LocalCertificate() {
        # Once the certificates have been been exported we can safely remove them from the store
        if ($CommonName.ToLower().StartsWith('cn=')) {
            # Remove CN from common name
            $CommonName = $CommonName.Substring(3)
        }
        $certs = Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object { $_.Subject -eq "CN=$CommonName" }
        foreach ($c in $certs) {
            Remove-Item $c.PSPath
        }
    }

    function New-SelfSignedAppCertificate() {

        #Remove and existing certificates with the same common name from personal and root stores
        #Need to be very wary of this as could break something
        if ($CommonName.ToLower().StartsWith('cn=')) {
            # Remove CN from common name
            $CommonName = $CommonName.Substring(3)
        }

        $certs = Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object { $_.Subject -eq "CN=$CommonName" }

        if ($null -ne $certs -and $certs.Length -gt 0) {

            if ($Force) {

                foreach ($c in $certs) {
                    Remove-Item $c.PSPath
                }

            } else {
                LOG $sbERROR "One or more certificates with the same common name (CN=$CommonName) are already located in the local certificate store. Use -Force to remove them";
                return $false
            }
        }

        $name = New-Object -com 'X509Enrollment.CX500DistinguishedName.1'
        $name.Encode("CN=$CommonName", 0)

        $key = New-Object -com 'X509Enrollment.CX509PrivateKey.1'
        $key.ProviderName = 'Microsoft RSA SChannel Cryptographic Provider'
        $key.KeySpec = 1
        $key.Length = 2048
        $key.SecurityDescriptor = 'D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)'
        $key.MachineContext = 1
        $key.ExportPolicy = 1 # This is required to allow the private key to be exported
        $key.Create()

        $serverauthoid = New-Object -com 'X509Enrollment.CObjectId.1'
        $serverauthoid.InitializeFromValue('1.3.6.1.5.5.7.3.1') # Server Authentication
        $ekuoids = New-Object -com 'X509Enrollment.CObjectIds.1'
        $ekuoids.add($serverauthoid)
        $ekuext = New-Object -com 'X509Enrollment.CX509ExtensionEnhancedKeyUsage.1'
        $ekuext.InitializeEncode($ekuoids)

        $cert = New-Object -com 'X509Enrollment.CX509CertificateRequestCertificate.1'
        $cert.InitializeFromPrivateKey(2, $key, '')
        $cert.Subject = $name
        $cert.Issuer = $cert.Subject
        $cert.NotBefore = $StartDate
        $cert.NotAfter = $EndDate
        $cert.X509Extensions.Add($ekuext)
        $cert.Encode()

        $enrollment = New-Object -com 'X509Enrollment.CX509Enrollment.1'
        $enrollment.InitializeFromRequest($cert)
        $certdata = $enrollment.CreateRequest(0)
        $enrollment.InstallResponse(2, $certdata, 0, '')
        return $true
    }

    function Export-PfxFile() {
        if ($CommonName.ToLower().StartsWith('cn=')) {
            # Remove CN from common name
            $CommonName = $CommonName.Substring(3)
        }
        if ($null -eq $Password) {
            $Password = Read-Host -Prompt 'Enter Password to protect private key' -AsSecureString
        }
        $cert = Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object { $_.Subject -eq "CN=$CommonName" }

        $null = Export-PfxCertificate -Cert $cert -Password $Password -FilePath "c:\Windows\Temp\$($CommonName).pfx"
        $null = Export-Certificate -Cert $cert -Type CERT -FilePath "c:\Windows\Temp\$CommonName.cer"
    }

    if (New-SelfSignedAppCertificate) {
        $cert = Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object { $_.Subject -eq "CN=$CommonName" }
        Export-PfxFile;

        return $cert.Thumbprint;

    } else {
        return 'ERROR: Failed to generate certificate.';
    }

}

function Get-NewComplexPassword([int]$passwordNrChars) {

    Process {

        $iterations = 0

        Do {
            if ($iterations -ge 20) {
                return 'ERROR: Complex password generation failed'
            }

            $iterations++
            $pwdBytes = @()
            $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider

            Do {

                [byte[]]$byte = [byte]1
                $rng.GetBytes($byte)

                if ($byte[0] -lt 33 -or $byte[0] -gt 126) {
                    CONTINUE
                }

                $pwdBytes += $byte[0]
            }

            While ($pwdBytes.Count -lt $passwordNrChars)

            $newPswd = ([char[]]$pwdBytes) -join ''
        }

        Until (Test-PasswordComplexity $newPswd)

        return $newPswd
    }
}

function Get-Base64EncodedString() {
    [Parameter(Mandatory = $false, ValueFromPipeline, HelpMessage = 'String to convert to Base64.')]
    param([string]$inputString)

    try {
        return [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($inputString))
    } catch {
        return  $inputString
    }
} Set-Alias -Name 'Encode-String' -Value Get-Base64EncodedString -Option ReadOnly -Scope Global -Force;

function Get-Base64DecodedString() {
    [Parameter(Mandatory = $false, ValueFromPipeline, HelpMessage = 'String to convert from Base64.')]
    param([string]$inputString)

    try {
        return [System.Text.Encoding]::Utf8.GetString([System.Convert]::FromBase64String($inputString))
    } catch {
        return  $inputString
    }
} Set-Alias -Name 'Decode-String' -Value Get-Base64DecodedString -Option ReadOnly -Scope Global -Force;

function Get-CompressedString() {
    [Parameter(Mandatory = $false, ValueFromPipeline, HelpMessage = 'String to compress using gzip.')]
    param([string]$inputString)

    begin {

        $memoryStream = [System.IO.MemoryStream]::new();
        $gzipStream = [System.IO.Compression.GZipStream]::new($memoryStream, [System.IO.Compression.CompressionMode]::Compress);
        $streamWriter = [System.IO.StreamWriter]::new($gzipStream);
    }
    process {

        if (-not([string]::IsNullOrEmpty($inputString))) {

            $streamWriter.WriteLine($inputString)
            $streamWriter.Flush()
            $streamWriter.Close()

            $gzipStream.Close()
            $memoryStream.Close()

            return [System.Convert]::ToBase64String($memoryStream.ToArray());

        } else {
            return $null;
        }
    }

} Set-Alias -Name 'Compress-String' -Value Get-CompressedString -Option ReadOnly -Scope Global -Force;

function Get-UnCompressedString() {
    [Parameter(Mandatory = $false, ValueFromPipeline, HelpMessage = 'String to uncompress using gzip.')]
    param([string]$inputString)

    if (-not([string]::IsNullOrEmpty($inputString))) {

        $memoryStream = [System.IO.MemoryStream]::new([Convert]::FromBase64String($inputString));
        $gzipStream = [System.IO.Compression.GZipStream]::new($memoryStream, [System.IO.Compression.CompressionMode]::Decompress);
        $streamReader = [System.IO.StreamReader]::new($gzipStream);

        $result = $streamReader.ReadToEnd();

        $streamReader.Close();
        $gzipStream.Close();
        $memoryStream.Close();

        return $result;

    } else {
        return $null;
    }

} Set-Alias -Name 'Expand-String' -Value Get-UnCompressedString -Option ReadOnly -Scope Global -Force;

function Get-AutomateEncryptedString() {
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$InputString = $(throw 'ERROR: Missing the InputString parameter'),
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$SaltString = $(throw 'ERROR: Missing the SaltString parameter')
    )

    begin {

        $_initializationVector = [byte[]](240, 3, 45, 29, 0, 76, 173, 59);
        $result = $null;
    }
    process {

        try {

            try {
                $numarray = [System.Text.Encoding]::UTF8.GetBytes($InputString)
            } catch {
                try {
                    $numarray = [System.Text.Encoding]::ASCII.GetBytes($InputString)
                } catch {
                    $result = "ERROR: Failed to parse input string: $($_.Exception.Message)"
                }
            }

            $ddd = New-Object System.Security.Cryptography.TripleDESCryptoServiceProvider;
            $ddd.key = (New-Object Security.Cryptography.MD5CryptoServiceProvider).ComputeHash([Text.Encoding]::UTF8.GetBytes($SaltString));
            $ddd.IV = $_initializationVector;
            $dd = $ddd.CreateEncryptor();

            $result = [System.Convert]::ToBase64String($dd.TransformFinalBlock($numarray, 0, ($numarray.Length)))

        } catch {
            $result = "ERROR: Failed to encrypt string: $($_.Exception.Message)";
        } finally {
            if ($dd) { try { $dd.Dispose() } catch { $dd.Clear() } }
            if ($ddd) { try { $ddd.Dispose() } catch { $ddd.Clear() } }
        }
    }
    end {
        return $result;
    }

} Set-Alias -Name 'Encrypt-String' -Value Get-AutomateEncryptedString -Option ReadOnly -Scope Global -Force;

function Get-AutomateDecryptedString() {
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$InputString = $(throw 'ERROR: Missing the InputString parameter'),
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$SaltString = $(throw 'ERROR: Missing the SaltString parameter')
    )

    begin {

        $_initializationVector = [byte[]](240, 3, 45, 29, 0, 76, 173, 59);
        $result = $null;
    }
    process {

        try {

            $numarray = [System.Convert]::FromBase64String($InputString)

            $ddd = New-Object System.Security.Cryptography.TripleDESCryptoServiceProvider
            $ddd.key = (New-Object Security.Cryptography.MD5CryptoServiceProvider).ComputeHash([Text.Encoding]::UTF8.GetBytes($SaltString))
            $ddd.IV = $_initializationVector
            $dd = $ddd.CreateDecryptor()

            $result = [System.Text.Encoding]::UTF8.GetString($dd.TransformFinalBlock($numarray, 0, ($numarray.Length)))

        } catch {
            $result = "ERROR: Failed to decrypt string: $($_.Exception.Message)"
        } finally {
            if ($dd) { try { $dd.Dispose() } catch { $dd.Clear() } }
            if ($ddd) { try { $ddd.Dispose() } catch { $ddd.Clear() } }
        }
    }
    end {
        return $result;
    }

} Set-Alias -Name 'Decrypt-String' -Value Get-AutomateDecryptedString -Option ReadOnly -Scope Global -Force;

function Get-MySQLEscapedString() {
    [Parameter(Mandatory = $false, ValueFromPipeline, HelpMessage = 'String to escape for MySQL insert.')]
    param([string]$inputString)

    if (-not([string]::IsNullOrEmpty($inputString))) {
        $stringBuilder = [System.Text.StringBuilder]::new();

        try {

            for ($index = 0; $index -lt $inputString.Length; $index++) {

                $char = $inputString[$index]

                switch ($char) {

                    "`0" {
                        [void]$stringBuilder.Append("\0");
                    }
                    "`b" {
                        [void]$stringBuilder.Append("\b");
                    }
                    "`t" {
                        [void]$stringBuilder.Append("\t");
                    }
                    "`n" {
                        [void]$stringBuilder.Append("\n");
                    }
                    "`r" {
                        [void]$stringBuilder.Append("\r");
                    }
                    "`u{001A}" {
                        [void]$stringBuilder.Append("\Z");
                    }
                    "#" {
                        [void]$stringBuilder.Append("\#");
                    }
                    ";" {
                        [void]$stringBuilder.Append("\;");
                    }
                    "\" {
                        [void]$stringBuilder.Append("\").Append($char);
                    }
                    "'" {
                        [void]$stringBuilder.Append("\").Append($char);
                    }
                    '"' {
                        [void]$stringBuilder.Append("\").Append($char);
                    }
                    '`' {
                        [void]$stringBuilder.Append("\").Append($char);
                    }
                    "´" {
                        [void]$stringBuilder.Append("\").Append($char);
                    }
                    "’" {
                        [void]$stringBuilder.Append("\").Append($char);
                    }
                    "‘" {
                        [void]$stringBuilder.Append("\").Append($char);
                    }
                    default {
                        if ($char -match "[\u0096\u0097\u0092\u0093\u0094\u0091\u0090]") {
                            [void]$stringBuilder.Append("\").Append($char);
                        } else {
                            [void]$stringBuilder.Append($char);
                        }
                    }
                }
            }

        } catch {
            return  $inputString -replace '\\', '\\' -replace '[''��]', "\'"
        }

        return $stringBuilder.ToString().Trim();
    } else {
        return $null;
    }
}
Set-Alias -Name 'Get-CS' -Value Get-MySQLEscapedString -Option ReadOnly -Scope Global -Force;
Set-Alias -Name 'Get-CleanString' -Value Get-MySQLEscapedString -Option ReadOnly -Scope Global -Force;

function Get-CleanFileName {
    param(
        [Parameter(Mandatory, ValueFromPipeline)][String]$Filename
    )

    try {

        $InvalidCharacters = [IO.Path]::GetInvalidFileNameChars() -join '';
        $ReplacePattern = '[{0}]' -f [RegEx]::Escape($InvalidCharacters);

        return ($Filename -replace $ReplacePattern, '_');

    } catch {

        Write-Warning "Failed to parse filename $($Filename) for invalid characters: $($_.Exception.Message)";

        return $Filename;
    }
}
Set-Alias -Name 'Get-CF' -Value Get-CleanFileName -Option ReadOnly -Scope Global -Force;

function Get-RealDate($string) {
    if ((-not([string]::IsNullOrEmpty($string))) -and $string -ne '1/1/0001 8:00:00 AM') {
        return $string
    } else {
        return ''
    }
}

function Get-LocalTime {
    [cmdletbinding()]
    [Outputtype([System.Datetime])]
    Param(
        [alias('dt')]
        [string]$Time
    )

    if (-not([string]::IsNullOrEmpty($Time))) {

        try {

            # This assumes that input string is in UTC format
            return $([TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($([datetime]::Parse($time)), 'Pacific Standard Time')).ToString("yyyy-MM-dd HH:mm:ss");

        } catch {

            try {

                # If that fails, this converts the parsed string to UTC
                return [TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($(([DateTime]$Time).ToUniversalTime()), 'Pacific Standard Time').ToString('yyyy-MM-dd HH:mm:ss');

            } catch {

                Write-Warning "WARNING: Failed to parse or convert string '$($Time)' to Local Time.";
                return $Time;
            }
        }

    } else {
        return '0000-00-00 00:00:00'
    }
}

function Get-SQLDateTime([DateTime]$Time) {
    return $Time.ToString("yyyy-MM-dd HH:mm:ss");
}

function Get-TitleCase([string]$string) {
    if (-not([string]::IsNullOrEmpty($string))) {
        return [cultureinfo]::GetCultureInfo("en-US").TextInfo.ToTitleCase($string)
    } else {
        return $null;
    }
}

function Get-FirstCharacterUpperCase([string]$string) {
    if (-not([string]::IsNullOrEmpty($string))) {
        return $string.Replace($string[0], $string[0].ToString().ToUpper())
    } else {
        return $null;
    }
} Set-Alias -Name 'Get-FCUC' -Value Get-FirstCharacterUpperCase -Option ReadOnly -Scope Global -Force;

function Get-HTMLOuterText([string]$htmlString) {

    try {

        $html = New-Object -ComObject "HTMLFile";
        $html.IHTMLDocument2_write($htmlString);

        return $html.body.outerText.Trim();

    } catch {
        return $htmlString;
    }
}

# Source: https://blog.idera.com/database-tools/get-text-file-encoding
function Get-FileEncoding {
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('FullName')]
        [ValidateScript({ if (-Not ($_ | Test-Path -PathType Leaf) ) { throw "The Path argument must be a file. Folder paths are not allowed." } return $true })]
        [string]$Path
    )

    begin {
        $bom = New-Object -TypeName System.Byte[](4);
        $file = New-Object System.IO.FileStream($Path, 'Open', 'Read');
    }

    process {

        try {

            $null = $file.Read($bom, 0, 4)
            $file.Close()
            $file.Dispose()

            $enc = [Text.Encoding]::ASCII;

            if ($bom[0] -eq 0x2b -and $bom[1] -eq 0x2f -and $bom[2] -eq 0x76) {
                $enc = [Text.Encoding]::UTF7
            } elseif ($bom[0] -eq 0xff -and $bom[1] -eq 0xfe) {
                $enc = [Text.Encoding]::Unicode
            } elseif ($bom[0] -eq 0xfe -and $bom[1] -eq 0xff) {
                $enc = [Text.Encoding]::BigEndianUnicode
            } elseif ($bom[0] -eq 0x00 -and $bom[1] -eq 0x00 -and $bom[2] -eq 0xfe -and $bom[3] -eq 0xff) {
                $enc = [Text.Encoding]::UTF32
            } elseif ($bom[0] -eq 0xef -and $bom[1] -eq 0xbb -and $bom[2] -eq 0xbf) {
                $enc = [Text.Encoding]::UTF8
            }

            return $Enc

        } catch {
            throw $_.Exception;
        }
    }
}

function Get-FolderSize {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]$Path,
        [ValidateSet('KB', 'MB', 'GB')]$Units = 'MB'
    )

    try {

        if ((Test-Path $Path) -and (Get-Item $Path).PSIsContainer) {
            $Measure = Get-ChildItem $Path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum
            $Sum = $Measure.Sum / "1$Units"
            [pscustomobject]@{
                'Path'         = $Path
                "Size($Units)" = $Sum
            }
        }

    } catch {
        throw $_
    }
} Set-Alias -Name 'GFS' -Value Get-FolderSize -Option ReadOnly -Scope Global -Force;

# Simple function for parsing a value that could be null, boolean, or string
function Get-TrueFalseInteger($InputValue) {
    if ($null -eq $InputValue) {
        return 0;
    } else {

        if ($InputValue -is [Boolean]) {
            return [int]$InputValue;
        } else {
            return [int]($InputValue.ToLower() -eq 'true');
        }
    }
} Set-Alias -Name 'TFINT' -Value Get-TrueFalseInteger -Option ReadOnly -Scope Global -Force;

function Get-UsernameFromSID($userNameOrSID) {

    if ($userNameOrSID -match 'S-1-.*') {

        try {

            $objSID = New-Object System.Security.Principal.SecurityIdentifier($userNameOrSID);
            $objUser = $objSID.Translate( [System.Security.Principal.NTAccount]);

            if (-not($null -eq $objUser)) {

                return $objUser.Value -replace '\\', '\\' ;

            } else {
                return ($userNameOrSID -replace '\\', '\\');
            }

        } catch {
            return ($userNameOrSID -replace '\\', '\\');
        }
    } else {
        return ($userNameOrSID -replace '\\', '\\');
    }
}

# Print a more detailed, readable exception message.
function Get-ExceptionReport {
    param(
        [Parameter(Mandatory, ValueFromPipeline)][object]$Exception,
        [switch]$includeStackTrace
    )

    $report = [System.Text.StringBuilder]::new();

    $IndentPadding = '';
    $IndentPadding = $IndentPadding.PadRight(18, ' ');

    [void]$report.AppendLine($('{0} {1}' -f $(("Message:").PadRight(18)), $Error[0].Exception.Message.Replace("`n", "`n$($IndentPadding)")));
    [void]$report.AppendLine($('{0} {1}' -f $(("Category:").PadRight(18)), $Error[0].CategoryInfo.Category));
    [void]$report.AppendLine($('{0} {1} ({2})' -f $(("Parameter:").PadRight(18)), $Error[0].Exception.ParameterName, $Error[0].ParameterType));
    [void]$report.AppendLine($('{0} {1}' -f $(("Error Id:").PadRight(18)), $Error[0].Exception.ErrorId));

    if(-not($null -eq $Error[0].InvocationInfo)){

        [void]$report.AppendLine($('{0} {1}' -f $(("Command:").PadRight(18)), $Error[0].InvocationInfo.MyCommand.Name));
        [void]$report.AppendLine($('{0} {1}' -f $(("Line:").PadRight(18)), $Error[0].InvocationInfo.Line.Replace("`n", "`n$($IndentPadding)")));
        [void]$report.AppendLine($('{0} {1}' -f $(("Source:").PadRight(18)), $Error[0].InvocationInfo.MyCommand.Source));
        [void]$report.AppendLine($('{0} {1}' -f $(("Position:").PadRight(18)), $Error[0].InvocationInfo.PositionMessage.Replace("`n", "`n$($IndentPadding)")));

    } elseif (-not($null -eq $Error[0].Exception.CommandInvocation)){

        [void]$report.AppendLine($('{0} {1}' -f $(("Command:").PadRight(18)), $Error[0].Exception.CommandInvocation.MyCommand.Name));
        [void]$report.AppendLine($('{0} {1}' -f $(("Line:").PadRight(18)), $Error[0].Exception.CommandInvocation.Line.Replace("`n", "`n$($IndentPadding)")));
        [void]$report.AppendLine($('{0} {1}' -f $(("Source:").PadRight(18)), $Error[0].Exception.CommandInvocation.MyCommand.Source));
        [void]$report.AppendLine($('{0} {1}' -f $(("Position:").PadRight(18)), $Error[0].Exception.CommandInvocation.PositionMessage.Replace("`n", "`n$($IndentPadding)")));
    }

    if(($includeStackTrace.IsPresent) -and -not($null -eq $Error[0].Exception.StackTrace)){
        [void]$report.AppendLine($('{0} {1}' -f $(("Stack Trace:").PadRight(18)), $Error[0].Exception.StackTrace.Replace("`n", "`n$($IndentPadding)")));
    }

    if(-not($null -eq $Error[0].Exception.InnerException)){

        if(-not($null -eq $Error[0].Exception.InnerException.ErrorRecord.FullyQualifiedErrorId)){
            [void]$report.AppendLine($('{0} {1}' -f $(("Inner Error:").PadRight(18)), $Error[0].Exception.InnerException.ErrorRecord.FullyQualifiedErrorId));
        }

        if(-not($null -eq $Error[0].Exception.InnerException.Message)){
            [void]$report.AppendLine($('{0} {1}' -f $(("Inner Message:").PadRight(18)), $Error[0].Exception.InnerException.Message.Replace("`n", "`n$($IndentPadding)")));
        }
    }

    return $report.ToString();
}


$WlanGetProfileListSig = @'
 
[DllImport("wlanapi.dll")]
public static extern uint WlanOpenHandle(
    [In] UInt32 clientVersion,
    [In, Out] IntPtr pReserved,
    [Out] out UInt32 negotiatedVersion,
    [Out] out IntPtr clientHandle
);
 
[DllImport("Wlanapi.dll")]
public static extern uint WlanCloseHandle(
    [In] IntPtr ClientHandle,
    IntPtr pReserved
);
 
[DllImport("wlanapi.dll", SetLastError = true, CallingConvention=CallingConvention.Winapi)]
public static extern uint WlanGetProfileList(
    [In] IntPtr clientHandle,
    [In, MarshalAs(UnmanagedType.LPStruct)] Guid interfaceGuid,
    [In] IntPtr pReserved,
    [Out] out IntPtr profileList
);
 
[DllImport("wlanapi.dll")]
public static extern uint WlanGetProfile(
    [In]IntPtr clientHandle,
    [In, MarshalAs(UnmanagedType.LPStruct)] Guid interfaceGuid,
    [In, MarshalAs(UnmanagedType.LPWStr)] string profileName,
    [In, Out] IntPtr pReserved,
    [Out, MarshalAs(UnmanagedType.LPWStr)] out string pstrProfileXml,
    [In, Out, Optional] ref uint flags,
    [Out, Optional] out uint grantedAccess
);
 
[DllImport("wlanapi.dll", EntryPoint = "WlanFreeMemory")]
public static extern void WlanFreeMemory(
    [In] IntPtr pMemory
);
 
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
public struct WLAN_CONNECTION_PARAMETERS
{
    public WLAN_CONNECTION_MODE wlanConnectionMode;
    public string strProfile;
    public DOT11_SSID[] pDot11Ssid;
    public DOT11_BSSID_LIST[] pDesiredBssidList;
    public DOT11_BSS_TYPE dot11BssType;
    public uint dwFlags;
}
 
public struct DOT11_BSSID_LIST
{
    public NDIS_OBJECT_HEADER Header;
    public ulong uNumOfEntries;
    public ulong uTotalNumOfEntries;
    public IntPtr BSSIDs;
}
 
public struct NDIS_OBJECT_HEADER
{
    public byte Type;
    public byte Revision;
    public ushort Size;
}
 
public struct WLAN_PROFILE_INFO_LIST
{
    public uint dwNumberOfItems;
    public uint dwIndex;
    public WLAN_PROFILE_INFO[] ProfileInfo;
 
    public WLAN_PROFILE_INFO_LIST(IntPtr ppProfileList)
    {
        dwNumberOfItems = (uint)Marshal.ReadInt32(ppProfileList);
        dwIndex = (uint)Marshal.ReadInt32(ppProfileList, 4);
        ProfileInfo = new WLAN_PROFILE_INFO[dwNumberOfItems];
        IntPtr ppProfileListTemp = new IntPtr(ppProfileList.ToInt64() + 8);
 
        for (int i = 0; i < dwNumberOfItems; i++)
        {
            ppProfileList = new IntPtr(ppProfileListTemp.ToInt64() + i * Marshal.SizeOf(typeof(WLAN_PROFILE_INFO)));
            ProfileInfo[i] = (WLAN_PROFILE_INFO)Marshal.PtrToStructure(ppProfileList, typeof(WLAN_PROFILE_INFO));
        }
    }
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_PROFILE_INFO
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string strProfileName;
    public WlanProfileFlags ProfileFLags;
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DOT11_SSID
{
    /// ULONG->unsigned int
    public uint uSSIDLength;
 
    /// UCHAR[]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string ucSSID;
}
 
public enum DOT11_BSS_TYPE
{
    Infrastructure = 1,
    Independent = 2,
    Any = 3,
}
 
public enum DOT11_PHY_TYPE
{
    dot11_phy_type_unknown = 0,
    dot11_phy_type_any = 0,
    dot11_phy_type_fhss = 1,
    dot11_phy_type_dsss = 2,
    dot11_phy_type_irbaseband = 3,
    dot11_phy_type_ofdm = 4,
    dot11_phy_type_hrdsss = 5,
    dot11_phy_type_erp = 6,
    dot11_phy_type_ht = 7,
    dot11_phy_type_vht = 8,
    dot11_phy_type_IHV_start = -2147483648,
    dot11_phy_type_IHV_end = -1,
}
 
public enum DOT11_AUTH_ALGORITHM
{
    DOT11_AUTH_ALGO_80211_OPEN = 1,
    DOT11_AUTH_ALGO_80211_SHARED_KEY = 2,
    DOT11_AUTH_ALGO_WPA = 3,
    DOT11_AUTH_ALGO_WPA_PSK = 4,
    DOT11_AUTH_ALGO_WPA_NONE = 5,
    DOT11_AUTH_ALGO_RSNA = 6,
    DOT11_AUTH_ALGO_RSNA_PSK = 7,
    DOT11_AUTH_ALGO_WPA3 = 8,
    DOT11_AUTH_ALGO_WPA3_SAE = 9,
    DOT11_AUTH_ALGO_OWE = 10,
    DOT11_AUTH_ALGO_WPA3_ENT = 11,
    DOT11_AUTH_ALGO_IHV_START = -2147483648,
    DOT11_AUTH_ALGO_IHV_END = -1,
}
 
public enum DOT11_CIPHER_ALGORITHM
{
    /// DOT11_CIPHER_ALGO_NONE -> 0x00
    DOT11_CIPHER_ALGO_NONE = 0,
 
    /// DOT11_CIPHER_ALGO_WEP40 -> 0x01
    DOT11_CIPHER_ALGO_WEP40 = 1,
 
    /// DOT11_CIPHER_ALGO_TKIP -> 0x02
    DOT11_CIPHER_ALGO_TKIP = 2,
 
    /// DOT11_CIPHER_ALGO_CCMP -> 0x04
    DOT11_CIPHER_ALGO_CCMP = 4,
 
    /// DOT11_CIPHER_ALGO_WEP104 -> 0x05
    DOT11_CIPHER_ALGO_WEP104 = 5,
 
    /// DOT11_CIPHER_ALGO_BIP -> 0x06
    DOT11_CIPHER_ALGO_BIP = 6,
 
    /// DOT11_CIPHER_ALGO_GCMP -> 0x08
    DOT11_CIPHER_ALGO_GCMP = 8,
 
    /// DOT11_CIPHER_ALGO_GCMP_256 -> 0x09
    DOT11_CIPHER_ALGO_GCMP_256 = 9,
 
    /// DOT11_CIPHER_ALGO_CCMP_256 -> 0x0a
    DOT11_CIPHER_ALGO_CCMP_256 = 10,
 
    /// DOT11_CIPHER_ALGO_BIP_GMAC_128 -> 0x0b
    DOT11_CIPHER_ALGO_BIP_GMAC_128 = 11,
 
    /// DOT11_CIPHER_ALGO_BIP_GMAC_256 -> 0x0c
    DOT11_CIPHER_ALGO_BIP_GMAC_256 = 12,
 
    /// DOT11_CIPHER_ALGO_BIP_CMAC_256 -> 0x0d
    DOT11_CIPHER_ALGO_BIP_CMAC_256 = 13,
 
    /// DOT11_CIPHER_ALGO_WPA_USE_GROUP -> 0x100
    DOT11_CIPHER_ALGO_WPA_USE_GROUP = 256,
 
    /// DOT11_CIPHER_ALGO_RSN_USE_GROUP -> 0x100
    DOT11_CIPHER_ALGO_RSN_USE_GROUP = 256,
 
    /// DOT11_CIPHER_ALGO_WEP -> 0x101
    DOT11_CIPHER_ALGO_WEP = 257,
 
    /// DOT11_CIPHER_ALGO_IHV_START -> 0x80000000
    DOT11_CIPHER_ALGO_IHV_START = -2147483648,
 
    /// DOT11_CIPHER_ALGO_IHV_END -> 0xffffffff
    DOT11_CIPHER_ALGO_IHV_END = -1,
}
 
public enum WLAN_CONNECTION_MODE
{
    wlan_connection_mode_profile,
    wlan_connection_mode_temporary_profile,
    wlan_connection_mode_discovery_secure,
    wlan_connection_mode_discovery_unsecure,
    wlan_connection_mode_auto,
    wlan_connection_mode_invalid,
}
 
[Flags]
public enum WlanConnectionFlag
{
    Default = 0,
    HiddenNetwork = 1,
    AdhocJoinOnly = 2,
    IgnorePrivayBit = 4,
    EapolPassThrough = 8,
    PersistDiscoveryProfile = 10,
    PersistDiscoveryProfileConnectionModeAuto = 20,
    PersistDiscoveryProfileOverwriteExisting = 40
}
 
[Flags]
public enum WlanProfileFlags
{
    AllUser = 0,
    GroupPolicy = 1,
    User = 2
}
 
public class ProfileInfo
{
    public string ProfileName;
    public string ConnectionMode;
    public string Authentication;
    public string Encryption;
    public string Password;
    public bool ConnectHiddenSSID;
    public string EAPType;
    public string ServerNames;
    public string TrustedRootCA;
    public string Xml;
}
 
[DllImport("Wlanapi.dll", SetLastError = true)]
public static extern uint WlanEnumInterfaces (
    [In] IntPtr hClientHandle,
    [In] IntPtr pReserved,
    [Out] out IntPtr ppInterfaceList
);
 
public struct WLAN_INTERFACE_INFO_LIST
{
    public uint dwNumberOfItems;
    public uint dwIndex;
    public WLAN_INTERFACE_INFO[] wlanInterfaceInfo;
    public WLAN_INTERFACE_INFO_LIST(IntPtr ppInterfaceInfoList)
    {
        dwNumberOfItems = (uint)Marshal.ReadInt32(ppInterfaceInfoList);
        dwIndex = (uint)Marshal.ReadInt32(ppInterfaceInfoList, 4);
        wlanInterfaceInfo = new WLAN_INTERFACE_INFO[dwNumberOfItems];
        IntPtr ppInterfaceInfoListTemp = new IntPtr(ppInterfaceInfoList.ToInt64() + 8);
        for (int i = 0; i < dwNumberOfItems; i++)
        {
            ppInterfaceInfoList = new IntPtr(ppInterfaceInfoListTemp.ToInt64() + i * Marshal.SizeOf(typeof(WLAN_INTERFACE_INFO)));
            wlanInterfaceInfo[i] = (WLAN_INTERFACE_INFO)Marshal.PtrToStructure(ppInterfaceInfoList, typeof(WLAN_INTERFACE_INFO));
        }
    }
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_INTERFACE_INFO
{
    public Guid Guid;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string Description;
    public WLAN_INTERFACE_STATE State;
}
 
public enum WLAN_INTERFACE_STATE {
    not_ready = 0,
    connected = 1,
    ad_hoc_network_formed = 2,
    disconnecting = 3,
    disconnected = 4,
    associating = 5,
    discovering = 6,
    authenticating = 7
}
 
[DllImport("Wlanapi.dll",SetLastError=true)]
public static extern uint WlanScan(
    IntPtr hClientHandle,
    ref Guid pInterfaceGuid,
    IntPtr pDot11Ssid,
    IntPtr pIeData,
    IntPtr pReserved
);
 
[DllImport("Wlanapi.dll")]
public static extern uint WlanSetInterface(
    IntPtr hClientHandle,
    ref Guid pInterfaceGuid,
    WLAN_INTF_OPCODE OpCode,
    uint dwDataSize,
    IntPtr pData ,
    IntPtr pReserved
);
 
public enum WLAN_INTF_OPCODE
{
    /// wlan_intf_opcode_autoconf_start -> 0x000000000
    wlan_intf_opcode_autoconf_start = 0,
 
    wlan_intf_opcode_autoconf_enabled,
 
    wlan_intf_opcode_background_scan_enabled,
 
    wlan_intf_opcode_media_streaming_mode,
 
    wlan_intf_opcode_radio_state,
 
    wlan_intf_opcode_bss_type,
 
    wlan_intf_opcode_interface_state,
 
    wlan_intf_opcode_current_connection,
 
    wlan_intf_opcode_channel_number,
 
    wlan_intf_opcode_supported_infrastructure_auth_cipher_pairs,
 
    wlan_intf_opcode_supported_adhoc_auth_cipher_pairs,
 
    wlan_intf_opcode_supported_country_or_region_string_list,
 
    wlan_intf_opcode_current_operation_mode,
 
    wlan_intf_opcode_supported_safe_mode,
 
    wlan_intf_opcode_certified_safe_mode,
 
    /// wlan_intf_opcode_autoconf_end -> 0x0fffffff
    wlan_intf_opcode_autoconf_end = 268435455,
 
    /// wlan_intf_opcode_msm_start -> 0x10000100
    wlan_intf_opcode_msm_start = 268435712,
 
    wlan_intf_opcode_statistics,
 
    wlan_intf_opcode_rssi,
 
    /// wlan_intf_opcode_msm_end -> 0x1fffffff
    wlan_intf_opcode_msm_end = 536870911,
 
    /// wlan_intf_opcode_security_start -> 0x20010000
    wlan_intf_opcode_security_start = 536936448,
 
    /// wlan_intf_opcode_security_end -> 0x2fffffff
    wlan_intf_opcode_security_end = 805306367,
 
    /// wlan_intf_opcode_ihv_start -> 0x30000000
    wlan_intf_opcode_ihv_start = 805306368,
 
    /// wlan_intf_opcode_ihv_end -> 0x3fffffff
    wlan_intf_opcode_ihv_end = 1073741823,
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WlanPhyRadioState
{
    public int dwPhyIndex;
    public Dot11RadioState dot11SoftwareRadioState;
    public Dot11RadioState dot11HardwareRadioState;
}
 
public enum Dot11RadioState : uint
{
    Unknown = 0,
    On,
    Off
}
 
public enum WLAN_OPCODE_VALUE_TYPE
{
    /// wlan_opcode_value_type_query_only -> 0
    wlan_opcode_value_type_query_only = 0,
 
    /// wlan_opcode_value_type_set_by_group_policy -> 1
    wlan_opcode_value_type_set_by_group_policy = 1,
 
    /// wlan_opcode_value_type_set_by_user -> 2
    wlan_opcode_value_type_set_by_user = 2,
 
    /// wlan_opcode_value_type_invalid -> 3
    wlan_opcode_value_type_invalid = 3
}
 
[DllImport("Wlanapi", EntryPoint = "WlanQueryInterface")]
public static extern uint WlanQueryInterface(
    [In] IntPtr hClientHandle,
    [In] ref Guid pInterfaceGuid,
    WLAN_INTF_OPCODE OpCode,
    IntPtr pReserved,
    [Out] out uint pdwDataSize,
    ref IntPtr ppData,
    IntPtr pWlanOpcodeValueType
);
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_CONNECTION_ATTRIBUTES
{
    /// WLAN_INTERFACE_STATE->_WLAN_INTERFACE_STATE
    public WLAN_INTERFACE_STATE isState;
 
    /// WLAN_CONNECTION_MODE->_WLAN_CONNECTION_MODE
    public WLAN_CONNECTION_MODE wlanConnectionMode;
 
    /// WCHAR[256]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string strProfileName;
 
    /// WLAN_ASSOCIATION_ATTRIBUTES->_WLAN_ASSOCIATION_ATTRIBUTES
    public WLAN_ASSOCIATION_ATTRIBUTES wlanAssociationAttributes;
 
    /// WLAN_SECURITY_ATTRIBUTES->_WLAN_SECURITY_ATTRIBUTES
    public WLAN_SECURITY_ATTRIBUTES wlanSecurityAttributes;
}
 
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DOT11_MAC_ADDRESS
{
     public byte one;
     public byte two;
     public byte three;
     public byte four;
     public byte five;
     public byte six;
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_ASSOCIATION_ATTRIBUTES
{
    /// DOT11_SSID->_DOT11_SSID
    public DOT11_SSID dot11Ssid;
 
    /// DOT11_BSS_TYPE->_DOT11_BSS_TYPE
    public DOT11_BSS_TYPE dot11BssType;
 
    /// DOT11_MAC_ADDRESS->UCHAR[6]
    //// public DOT11_MAC_ADDRESS dot11Bssid;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public byte[] _dot11Bssid;
 
    /// DOT11_PHY_TYPE->_DOT11_PHY_TYPE
    public DOT11_PHY_TYPE dot11PhyType;
 
    /// ULONG->unsigned int
    public uint uDot11PhyIndex;
 
    /// WLAN_SIGNAL_QUALITY->ULONG->unsigned int
    public uint wlanSignalQuality;
 
    /// ULONG->unsigned int
    public uint ulRxRate;
 
    /// ULONG->unsigned int
    public uint ulTxRate;
}
 
[StructLayout(LayoutKind.Sequential)]
public struct WLAN_SECURITY_ATTRIBUTES
{
    /// <summary>
    /// BOOL->int
    /// </summary>
    [MarshalAs(UnmanagedType.Bool)]
    public bool bSecurityEnabled;
 
    /// <summary>
    /// BOOL->int
    /// </summary>
    [MarshalAs(UnmanagedType.Bool)]
    public bool bOneXEnabled;
 
    /// <summary>
    /// DOT11_AUTH_ALGORITHM->_DOT11_AUTH_ALGORITHM
    /// </summary>
    public DOT11_AUTH_ALGORITHM dot11AuthAlgorithm;
 
    /// <summary>
    /// DOT11_CIPHER_ALGORITHM->_DOT11_CIPHER_ALGORITHM
    /// </summary>
    public DOT11_CIPHER_ALGORITHM dot11CipherAlgorithm;
}
'@


$WlanGetProfileListErrorCodes = @'
    ErrorOpeningHandle = Error opening WiFi handle. Message {0}
    HandleClosed = Handle successfully closed.
    ErrorClosingHandle = Error closing handle. Message {0}
    ErrorGettingProfile = Error getting profile info. Error code: {0}
    ProfileNotFound = Profile {0} not found. Note ProfileName is case sensitive.
    ErrorDeletingProfile = Error deleting profile. Message {0}
    ShouldProcessDelete = Deletion of profile {0}
    ErrorWlanConnect = Error connecting to {0} : {1}
    SuccessWlanConnect = Successfully connected to {0} : {1}
    ErrorReasonCode = Failed to format reason code. Error message: {0}
    ErrorFreeMemory = Failed to free memory. Error message: {0}
    ErrorGetAvailableNetworkList = Error invoking WlanGetAvailableNetworkList. Message {0}
    ErrorWiFiInterfaceNotFound = Wi-Fi interface not found on the system.
    ErrorNotWiFiAdapter = Adapter with name: {0} is not a WiFi capable.
    ErrorNoWiFiAdaptersFound = No wifi interfaces found.
    ErrorMoreThanOneInterface = More than one Wi-Fi interface found. Please specify a specific interface.
    ErrorNeedSingleAdapterName = More than one Wi-Fi adapter found. Please specify a single adapter name.
    ErrorFailedWithExitCode = Failed with exit code {0}.
'@
;

function Get-WiFiProfile {
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param (
        [Parameter(Position = 0)][System.String[]]$ProfileName,
        [Parameter()][System.String]$WiFiAdapterName,
        [Parameter()][Switch]$ClearKey
    )

    $ErrorCodes = ConvertFrom-StringData $WlanGetProfileListErrorCodes;

    $null = Add-Type -MemberDefinition $WlanGetProfileListSig -Name ProfileManagement -Namespace WiFi -Using System.Text -PassThru;

    function New-WiFiHandle {
        [CmdletBinding()]
        [OutputType([System.IntPtr])]
        param()

        $maxClient = 2;
        [Ref]$negotiatedVersion = 0;
        $clientHandle = [IntPtr]::zero;

        $result = [WiFi.ProfileManagement]::WlanOpenHandle($maxClient, [IntPtr]::Zero, $negotiatedVersion, [ref] $clientHandle);

        if ($result -eq 0) {
            return $clientHandle;
        } else {
            throw $($ErrorCodes.ErrorOpeningHandle -f $(Format-Win32Exception -ReturnCode $result));
        }
    }

    function Remove-WiFiHandle {
        [OutputType([void])]
        [CmdletBinding()]
        param(
            [Parameter()][System.IntPtr]$ClientHandle
        )

        $result = [WiFi.ProfileManagement]::WlanCloseHandle($ClientHandle, [IntPtr]::zero);

        if ($result -eq 0) {
            Write-Verbose -Message ($ErrorCodes.HandleClosed);
        } else {
            throw $($ErrorCodes.ErrorClosingHandle -f $(Format-Win32Exception -ReturnCode $result));
        }
    }

    function Format-Win32Exception {
        [OutputType([System.String])]
        [CmdletBinding()]
        param(
            [Parameter(Mandatory = $true)][System.Int32]$ReturnCode
        )

        return [System.ComponentModel.Win32Exception]::new($ReturnCode).Message;
    }

    function Format-WiFiReasonCode {
        [OutputType([System.String])]
        [Cmdletbinding()]
        param(
            [Parameter()][System.IntPtr]$ReasonCode
        )

        $stringBuilder = New-Object -TypeName Text.StringBuilder;
        $stringBuilder.Capacity = 1024;

        $result = [WiFi.ProfileManagement]::WlanReasonCodeToString($ReasonCode.ToInt32(), $stringBuilder.Capacity, $stringBuilder, [IntPtr]::zero);

        if ($result -ne 0) {
            Write-Error -Message ($ErrorCodes.ErrorReasonCode -f $(Format-Win32Exception -ReturnCode $result));
        }

        return $stringBuilder.ToString();
    }

    function Invoke-WlanFreeMemory {
        [OutputType([void])]
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)][System.IntPtr[]]$Pointer
        )

        foreach ($ptr in $Pointer) {
            if ($ptr -ne 0) {
                try {
                    [WiFi.ProfileManagement]::WlanFreeMemory($ptr);
                } catch {
                    throw $($ErrorCodes.ErrorFreeMemory -f $errorMessage);
                }
            }
        }
    }

    function Get-WiFiProfileInfo {
        [OutputType([System.Management.Automation.PSCustomObject])]
        [CmdletBinding()]
        param(
            [Parameter()][System.String]$ProfileName,
            [Parameter()][System.Guid]$InterfaceGuid,
            [Parameter()][System.IntPtr]$ClientHandle,
            [System.Int16]$WlanProfileFlags
        )

        begin {
            [String] $pstrProfileXml = $null;
            $wlanAccess = 0;
            $WlanProfileFlagsInput = $WlanProfileFlags;
        }
        process {
            $result = [WiFi.ProfileManagement]::WlanGetProfile($ClientHandle, $InterfaceGuid, $ProfileName, [IntPtr]::Zero, [ref] $pstrProfileXml, [ref] $WlanProfileFlags, [ref] $wlanAccess);

            if ($result -ne 0) {
                throw $($ErrorCodes.ErrorGettingProfile -f $(Format-Win32Exception -ReturnCode $result));
            }

            $wlanProfile = [xml] $pstrProfileXml;

            $password = $null;

            if ($WlanProfileFlagsInput -eq 13) {
                $password = $wlanProfile.WLANProfile.MSM.security.sharedKey.keyMaterial;
            }

            $connectHiddenSSID = $false;

            if ([bool]::TryParse($wlanProfile.WLANProfile.SSIDConfig.nonBroadcast, [ref] $null)) {
                $connectHiddenSSID = [bool]::Parse($wlanProfile.WLANProfile.SSIDConfig.nonBroadcast);
            }

            $eapType = $null;

            if ($wlanProfile.WLANProfile.MSM.security.authEncryption.useOneX -eq 'true') {

                switch ($wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.EapMethod.Type.InnerText) {

                    '25' {
                        $eapType = 'PEAP'; #EAP-PEAP (MSCHAPv2)
                    }
                    '13' {
                        $eapType = 'TLS'; #EAP-TLS
                    }
                    Default {
                        $eapType = 'Unknown';
                    }
                }
            }

            # Parse Validation Server Name
            if ($null -ne $eapType) {

                switch ($eapType) {

                    'PEAP' {
                        $serverNames = $wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.Eap.EapType.ServerValidation.ServerNames;
                    }
                    'TLS' {
                        $node = $wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.SelectNodes("//*[local-name()='ServerNames']");
                        $serverNames = $node[0].InnerText;
                    }
                }
            }

            # Parse Validation TrustedRootCA
            if ($null -ne $eapType) {

                switch ($eapType) {

                    'PEAP' {
                        $trustedRootCa = ([string] ($wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.Eap.EapType.ServerValidation.TrustedRootCA -replace ' ', [string]::Empty)).ToLower();
                    }
                    'TLS' {
                        $node = $wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.SelectNodes("//*[local-name()='TrustedRootCA']");
                        $trustedRootCa = ([string] ($node[0].InnerText -replace ' ', [string]::Empty)).ToLower();
                    }
                }
            }

            [WiFi.ProfileManagement+ProfileInfo]@{
                ProfileName       = $wlanProfile.WLANProfile.SSIDConfig.SSID.name
                ConnectionMode    = $wlanProfile.WLANProfile.connectionMode
                Authentication    = $wlanProfile.WLANProfile.MSM.security.authEncryption.authentication
                Encryption        = $wlanProfile.WLANProfile.MSM.security.authEncryption.encryption
                Password          = $password
                ConnectHiddenSSID = $connectHiddenSSID
                EAPType           = $eapType
                ServerNames       = $serverNames
                TrustedRootCA     = $trustedRootCa
                Xml               = $pstrProfileXml
            }
        }
        end {
            $xmlPtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalAuto($pstrProfileXml);
            Invoke-WlanFreeMemory -Pointer $xmlPtr;
        }
    }

    function Get-WiFiInterface {
        [CmdletBinding()]
        [OutputType([WiFi.ProfileManagement+WLAN_INTERFACE_INFO])]
        param ()

        $interfaceListPtr = 0;
        $clientHandle = New-WiFiHandle;

        try {

            [void] [WiFi.ProfileManagement]::WlanEnumInterfaces($clientHandle, [IntPtr]::zero, [ref] $interfaceListPtr);

            $wiFiInterfaceList = [WiFi.ProfileManagement+WLAN_INTERFACE_INFO_LIST]::new($interfaceListPtr);

            foreach ($wlanInterfaceInfo in $wiFiInterfaceList.wlanInterfaceInfo) {
                [WiFi.ProfileManagement+WLAN_INTERFACE_INFO] $wlanInterfaceInfo;
            }

        } catch {
            Write-Error $PSItem;
        } finally {
            Remove-WiFiHandle -ClientHandle $clientHandle;
        }
    }

    function Get-InterfaceInfo {
        [CmdletBinding()]
        param(
            [Parameter()][System.String]$WiFiAdapterName
        )

        $result = [System.Collections.Generic.List[System.Object]]::new();
        $wifiAdapters = [System.Collections.Generic.List[System.Object]]::new();
        $getNetAdapterParams = [System.Collections.Generic.List[hashtable]]::new();

        $wifiInterfaces = Get-WiFiInterface;

        if ([string]::IsNullOrWhiteSpace($WiFiAdapterName)) {

            foreach ($wifiInterface in $wifiInterfaces) {
                $getNetAdapterParams.Add(@{InterfaceDescription = $wifiInterface.Description });
            }

        } else {
            $getNetAdapterParams.Add(@{Name = $WiFiAdapterName });
        }

        foreach ($getNetAdapterParam in $getNetAdapterParams) {
            $wifiAdapters = Get-NetAdapter @getNetAdapterParam;
        }

        # ensure we are using wifi adaptors
        foreach ($wifiAdapter in $wifiAdapters) {
            if ($wifiAdapter.InterfaceGuid -notin $wifiInterfaces.Guid) {
                Write-Error -Message ($ErrorCodes.ErrorNotWiFiAdapter -f $wifiAdapter.Name);
            } else {
                $result.Add($wifiAdapter);
            }
        }

        if ($result.Count -eq 0) {
            throw $ErrorCodes.ErrorNoWiFiAdaptersFound;
        }

        return $result;
    }

    function Add-DefaultProperty {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory)][object]$InputObject,
            [Parameter(Mandatory)][object]$InterfaceInfo
        )

        Add-Member -InputObject $InputObject -MemberType 'NoteProperty' -Name 'WiFiAdapterName' -Value $InterfaceInfo.Name -Force;
        Add-Member -InputObject $InputObject -MemberType 'NoteProperty' -Name 'InterfaceGuid' -Value $InterfaceInfo.InterfaceGuid -Force;

        if ($InputObject -is [WiFi.ProfileManagement+WLAN_CONNECTION_ATTRIBUTES]) {
            $apMac = [System.BitConverter]::ToString($InputObject.wlanAssociationAttributes._dot11Bssid);
            Add-Member -InputObject $InputObject -MemberType 'NoteProperty' -Name 'APMacAddress' -Value $apMac -Force;
        }

        return $InputObject;
    }

    try {

        $result = [System.Collections.Generic.List[System.Object]]::new();

        $profileListPointer = 0;
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName;
        $clientHandle = New-WiFiHandle;

        if ($ClearKey) {
            $wlanProfileFlags = 13;
        } else {
            $wlanProfileFlags = 0;
        }

        if (!$ProfileName) {
            foreach ($interface in $interfaceInfo) {
                [void] [WiFi.ProfileManagement]::WlanGetProfileList($clientHandle, $interface.InterfaceGuid, [IntPtr]::zero, [ref] $profileListPointer);
                $wiFiProfileList = [WiFi.ProfileManagement+WLAN_PROFILE_INFO_LIST]::new($profileListPointer);
                $ProfileName = ($wiFiProfileList.ProfileInfo).strProfileName;
            }
        }

        foreach ($wiFiProfile in $ProfileName) {
            foreach ($interface in $interfaceInfo) {
                $profileInfo = Get-WiFiProfileInfo -ProfileName $wiFiProfile -InterfaceGuid $interface.InterfaceGuid -ClientHandle $clientHandle -WlanProfileFlags $wlanProfileFlags;
                $result.Add($(Add-DefaultProperty -InputObject $profileInfo -InterfaceInfo $interface));
            }
        }

        return $result;

    } catch {
        Write-Error $PSItem;
    } finally {
        if (-not($null -eq $clientHandle)) {
            Remove-WiFiHandle -ClientHandle $clientHandle;
        }
    }
}

#endregion Get

#region Initialize

function Initialize-ModulePreReqs() {

    try {
        $null = if (!(Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) {
            $null = Install-PackageProvider -Name NuGet -Confirm:$false -Force;
        } else {
            Write-Verbose 'NuGet is installed.'
        }

        try {

            $null = if ((Get-PSRepository -Name PSGallery -ErrorAction Stop).InstallationPolicy -ne 'Trusted') {
                Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop;
            } else {
                Write-Verbose 'PSGallery is trusted.'
            }

        } catch {

            if ($_.Exception.Message -match "No repository with the name 'PSGallery' was found") {
                Remove-Item -Path C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Windows\PowerShell\PowerShellGet\PSRepositories.xml -Force -Confirm:$false;

                $null = if ($null -eq $(Get-PSRepository -WarningAction SilentlyContinue | Select-Object -ExpandProperty Name)) {
                    $null = Register-PSRepository -Default;
                    Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop;
                } else {
                    Write-Verbose 'PSGallery is trusted.'
                }
            }
        }

        $null = if (-not(Get-Module PowerShellGet -ListAvailable -ErrorAction SilentlyContinue | Where-Object { ($_.Version.Major -ge 2) -and ($_.Version.Minor -ge 2) })) {
            $null = Install-Module PowerShellGet -Scope AllUsers -Force -Confirm:$false;
        } else {
            Write-Verbose 'PowerShellGet is installed.'
        }

        $null = if (-not(Get-Module PackageManagement -ListAvailable -ErrorAction SilentlyContinue | Where-Object { ($_.Version.Minor -ge 4) -and ($_.Version.Build -ge 8) })) {
            $null = Install-Module PackageManagement -Scope AllUsers -Force -Confirm:$false;
        } else {
            Write-Verbose 'PackageManagement is installed.'
        }

        return $true;

    } catch {
        return $false;
    }
}

function Initialize-Module() {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory, HelpMessage = 'Module that should be installed or updated.')]
        [ValidateNotNullorEmpty()]
        [string]$moduleName,
        [Parameter(Position = 1, Mandatory = $false, HelpMessage = 'Minimum version. If specified, outdated modules will be updated.')]
        [ValidateNotNullorEmpty()]
        [string]$minVersion,
        [Parameter(Mandatory = $false, HelpMessage = "Perform the import command with the -Force flag.")]
        [switch]$Force
    )

    begin {

        $Action = $null;

        if ([string]::IsNullOrEmpty($minVersion)) {

            $Existing = Get-InstalledModule -Name $moduleName -ErrorAction SilentlyContinue;
            $ModuleInfo = Find-Module -Name $moduleName -AllowPrerelease -ErrorAction SilentlyContinue;

            if ($null -eq $Existing) {
                $Action = "INSTALL"; Write-Verbose "$moduleName is NOT installed.";
            } else {
                $Action = "IMPORT"; Write-Verbose "$moduleName is installed. Version: $($Existing.Version)";
            }

        } else {

            $Existing = Get-InstalledModule -Name $moduleName -MinimumVersion $minVersion -AllowPrerelease -ErrorAction SilentlyContinue;
            $ModuleInfo = Find-Module -Name $moduleName -MinimumVersion $minVersion -AllowPrerelease -ErrorAction SilentlyContinue;

            if ($null -eq $Existing) {

                $Existing = Get-InstalledModule -Name $moduleName -ErrorAction SilentlyContinue;

                if ($null -eq $ModuleInfo) {
                    $ModuleInfo = Find-Module -Name $moduleName -AllowPrerelease -ErrorAction SilentlyContinue;
                }

                if ($null -eq $Existing) {
                    $Action = "INSTALL"; Write-Verbose "$moduleName is NOT installed.";
                } else {
                    $Action = "UPDATE"; Write-Verbose "$moduleName IS installed but OUTDATED.";
                }

            } else {
                $Action = "IMPORT"; Write-Verbose "$moduleName is installed. Version: $($Existing.Version)";
            }
        }
    }

    process {

        switch ($Action) {

            'INSTALL' {

                try {

                    if ($null -eq $ModuleInfo) {

                        return "ERROR: No matched found for module $moduleName"

                    } else {

                        if ($ModuleInfo.AdditionalMetadata.requireLicenseAcceptance) {
                            Install-Module -Name $moduleName -Scope AllUsers -AllowPrerelease -AllowClobber -AcceptLicense -ErrorAction Stop;
                        } else {
                            Install-Module -Name $moduleName -Scope AllUsers -AllowPrerelease -AllowClobber -ErrorAction Stop;
                        }
                    }

                    $Existing = Get-Module -FullyQualifiedName @{ ModuleName = $moduleName; ModuleVersion = $($ModuleInfo.Version -replace '-beta\d+') } -ListAvailable -ErrorAction SilentlyContinue;

                } catch {
                    return "ERROR: Failed to install required module: $($moduleName) : $($_.Exception.Message)"
                }
            }

            'UPDATE' {

                # Check if the module is loaded and unload it to try and make sure it updates correctly.

                if (Get-Module $moduleName) {
                    Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue;
                }

                $Existing = $null;

                try {

                    if ($ModuleInfo.AdditionalMetadata.requireLicenseAcceptance) {
                        Update-Module -Name $moduleName -Scope AllUsers -AllowPrerelease -AcceptLicense -RequiredVersion $ModuleInfo.Version -ErrorAction Stop
                    } else {
                        Update-Module -Name $moduleName -Scope AllUsers -AllowPrerelease -RequiredVersion $ModuleInfo.Version -ErrorAction Stop;
                    }

                    $Existing = Get-Module -FullyQualifiedName @{ ModuleName = $moduleName; ModuleVersion = $ModuleInfo.Version } -ListAvailable -ErrorAction SilentlyContinue;

                    if (-not($null -eq $Existing)) {

                        try {

                            # Attempt to clean up old versions, may or may not work.
                            Get-InstalledModule -Name $moduleName -AllVersions | Where-Object { $_.Version -ne $Existing.Version } | ForEach-Object { Uninstall-Module -InputObject $_ -ErrorAction SilentlyContinue }

                        } catch {
                            Write-Verbose "Failed to uninstall outdated version(s) of $modulename. Errors were suppressed.";
                        }
                    }

                } catch {
                    return "ERROR: Failed to update required module: $($moduleName) : $($_.Exception.Message)";
                }
            }

            default {
                $ModuleInfo = $Existing;
                $Existing = try { Get-Module -FullyQualifiedName @{ ModuleName = $ModuleInfo.Name; ModuleVersion = $($ModuleInfo.Version -replace '-beta\d+') } -ListAvailable -ErrorAction SilentlyContinue; } catch { Get-Module -Name $ModuleInfo.Name -ListAvailable }
            }
        }

        try {

            if ($Force.IsPresent) {

                Write-Verbose "The Force flag was set.";
                Import-Module -Name $Existing.Name -Force -ErrorAction Stop;

            } else {
                Import-Module -ModuleInfo $Existing -ErrorAction Stop;
            }

            return "Imported $($moduleName) version $($Existing.Version)";

        } catch {
            return "ERROR: Failed to import required module: $($moduleName) : $($_.Exception.Message)"
        }
    }
}

function Initialize-ModuleCurrentUser() {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory, HelpMessage = 'Module that should be installed or updated.')]
        [ValidateNotNullorEmpty()]
        [string]$moduleName,
        [Parameter(Position = 1, Mandatory = $false, HelpMessage = 'Minimum version. If specified, outdated modules will be updated.')]
        [ValidateNotNullorEmpty()]
        [string]$minVersion
    )

    begin {

        $Action = $null;

        if ([string]::IsNullOrEmpty($minVersion)) {

            $Existing = Get-Module -Name $moduleName -ListAvailable -ErrorAction SilentlyContinue;
            $ModuleInfo = Find-Module -Name $moduleName -AllowPrerelease -ErrorAction SilentlyContinue;

            if ($null -eq $Existing) {
                $Action = "INSTALL"; Write-Verbose "$moduleName is NOT installed.";
            } else {
                $Action = "IMPORT";
            }

        } else {

            $Existing = Get-Module -FullyQualifiedName @{ ModuleName = $moduleName; ModuleVersion = $minVersion };
            $ModuleInfo = Find-Module -Name $moduleName -MinimumVersion $minVersion -AllowPrerelease -ErrorAction SilentlyContinue;

            if ($null -eq $Existing) {

                $Existing = Get-Module -Name $moduleName -ListAvailable -ErrorAction SilentlyContinue;

                if ($null -eq $ModuleInfo) {
                    $ModuleInfo = Find-Module -Name $moduleName -AllowPrerelease -ErrorAction SilentlyContinue;
                }

                if ($null -eq $Existing) {
                    $Action = "INSTALL"; Write-Verbose "$moduleName is NOT installed.";
                } else {
                    $Action = "UPDATE"; Write-Verbose "$moduleName IS installed but OUTDATED.";
                }

            } else {
                $Action = "IMPORT"; # Correct version is already installed.
            }
        }
    }

    process {

        switch ($Action) {

            'INSTALL' {

                try {

                    if ($null -eq $ModuleInfo) {

                        return "ERROR: No matched found for module $moduleName"

                    } else {

                        if ($ModuleInfo.AdditionalMetadata.requireLicenseAcceptance) {
                            Install-Module -Name $moduleName -Scope CurrentUser -AllowPrerelease -AllowClobber -AcceptLicense -ErrorAction Stop;
                        } else {
                            Install-Module -Name $moduleName -Scope CurrentUser -AllowPrerelease -AllowClobber -ErrorAction Stop;
                        }
                    }

                    $Existing = Get-Module -FullyQualifiedName @{ ModuleName = $moduleName; ModuleVersion = $ModuleInfo.Version } -ListAvailable -ErrorAction SilentlyContinue;

                } catch {
                    return "ERROR: Failed to install required module: $($moduleName) : $($_.Exception.Message)"
                }
            }

            'UPDATE' {

                # Check if the module is loaded and unload it to try and make sure it updates correctly.

                if (Get-Module $moduleName) {
                    Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue
                }

                try {

                    if ($ModuleInfo.AdditionalMetadata.requireLicenseAcceptance) {
                        Update-Module -Name $moduleName -Scope CurrentUser -AllowPrerelease -AcceptLicense -RequiredVersion $ModuleInfo.Version -ErrorAction Stop
                    } else {
                        Update-Module -Name $moduleName -Scope CurrentUser -AllowPrerelease -RequiredVersion $ModuleInfo.Version -ErrorAction Stop;
                    }

                    $Existing = Get-Module -FullyQualifiedName @{ ModuleName = $moduleName; ModuleVersion = $ModuleInfo.Version } -ListAvailable -ErrorAction SilentlyContinue;

                    if (-not($null -eq $Existing)) {

                        try {

                            # Attempt to clean up old versions, may or may not work.
                            Get-Module -Name $moduleName -ListAvailable | Where-Object { $_.Version -ne $Existing.Version } | Uninstall-Module -ErrorAction SilentlyContinue;

                        } catch {
                            Write-Verbose "Failed to uninstall outdated version(s) of $modulename. Errors were suppressed.";
                        }
                    }

                } catch {
                    return "ERROR: Failed to update required module: $($moduleName) : $($_.Exception.Message)";
                }
            }

            default {
                $ModuleInfo = $Existing;
                $Existing = Get-Module -FullyQualifiedName @{ ModuleName = $moduleName; ModuleVersion = $ModuleInfo.Version } -ListAvailable -ErrorAction SilentlyContinue;
            }
        }

        try {

            Import-Module -ModuleInfo $Existing -ErrorAction Stop;

            $Module = Get-Module -Name $moduleName;

            if (-not($null -eq $Module)) {
                return "Imported $($moduleName) version $($Existing.Version)";
            } else {
                return "ERROR: Failed to import required module: $($moduleName)"
            }

        } catch {
            return "ERROR: Failed to import required module: $($moduleName) : $($_.Exception.Message)"
        }
    }
}

function Initialize-EventLog {
    param(
        [Parameter(Mandatory = $true)][string]$logSource,
        [Parameter(Mandatory = $true)][string]$logName
    )

    try {

        if (-not([System.Diagnostics.EventLog]::Exists($logName))) {

            if ([System.Diagnostics.EventLog]::SourceExists($logSource)) {

                $existing = [System.Diagnostics.EventLog]::LogNameFromSourceName($logSource, '.')

                if ($existing -ne $logName) {

                    [System.Diagnostics.EventLog]::DeleteEventSource($logSource)
                    [System.Diagnostics.EventLog]::Delete($existing)
                }
            }

            New-EventLog -LogName $logName -Source $logSource -ErrorAction Stop
            Write-EventLog -log $logName -Source $logSource -EntryType Information -EventId 1 -Message "$logName Event Log Initialized"
        }

        if (-not([System.Diagnostics.EventLog]::SourceExists($logSource))) {

            [System.Diagnostics.EventLog]::CreateEventSource($logSource, $logName)

        }

        return "Event log $logName and source $logSource prepared."

    } catch {
        return $("Failed to add $logName event log: {0}" -f $_.Exception.Message)
    }
}

#endregion Initialize

#region Invoke

function Invoke-Process {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$FilePath,
        [Parameter(Mandatory = $false)][ValidateNotNullOrEmpty()][string]$ArgumentList
    )

    $ErrorActionPreference = 'Stop';

    try {

        $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)";
        $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)";

        Write-Verbose "StdOut Temp: $stdOutTempFile"
        Write-Verbose "StdErr Temp: $stdErrTempFile"

        [hashtable]$startProcessParams = @{}

        $startProcessParams.FilePath = $FilePath
        $startProcessParams.RedirectStandardError = $stdErrTempFile
        $startProcessParams.RedirectStandardOutput = $stdOutTempFile
        $startProcessParams.Wait = $true;
        $startProcessParams.PassThru = $true;
        $startProcessParams.NoNewWindow = $true;

        if (-not $null -eq $ArgumentList) {
            $startProcessParams.ArgumentList = $ArgumentList;
            Write-Verbose "Arguments: $ArgumentList"
        }

        if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) {

            $cmd = Start-Process @startProcessParams;

            $cmdOutput = Get-Content -Path $stdOutTempFile -Raw;
            $cmdError = Get-Content -Path $stdErrTempFile -Raw;

            $Results = @{

                ExitCode = $cmd.ExitCode;
                Error    = $(if (-not([string]::IsNullOrEmpty($cmdError))) { $cmdError.Trim() } else { $null })
                Output   = $(if (-not([string]::IsNullOrEmpty($cmdOutput))) { $cmdOutput.Trim() } else { $null })
            }

            return $(New-Object PSObject -Property $Results)
        }
    } catch {
        $PSCmdlet.ThrowTerminatingError($_)
    } finally {
        Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore
    }
}

#endregion Invoke

#region New

function New-LogEntry() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][System.Text.StringBuilder]$stringBuilder,
        [Parameter(Position = 1, Mandatory)][string]$dataToLog,
        [Parameter(Position = 2)][bool]$simple = $false,
        [switch]$NoTime
    )

    if ($NoTime.IsPresent) {
        [void]$stringBuilder.AppendFormat(':: {0}', $dataToLog.Trim()).AppendLine();
    } else {
        [void]$stringBuilder.AppendFormat('[{0}] : {1}', $(New-TimeStamp -simple $simple), $dataToLog.Trim()).AppendLine();
    }

} Set-Alias -Name 'LOG' -Value New-LogEntry -Option ReadOnly -Scope Global -Force;

function New-LogHeader() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][System.Text.StringBuilder]$stringBuilder,
        [Parameter(Position = 1, Mandatory)][string]$HeaderText,
        [Parameter(Position = 2)][bool]$simple = $false
    )

    New-LogEntry $stringBuilder $HeaderLine $simple;
    New-LogEntry $stringBuilder $HeaderText $simple;
    New-LogEntry $stringBuilder $HeaderLine $simple;
} Set-Alias -Name 'LOGH' -Value New-LogHeader -Option ReadOnly -Scope Global -Force;

function New-LogFooter() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][System.Text.StringBuilder]$stringBuilder,
        [Parameter(Position = 1, Mandatory)][string]$FooterText,
        [Parameter(Position = 2)][bool]$simple = $false
    )

    New-LogEntry $stringBuilder $FooterLine $simple;
    New-LogEntry $stringBuilder $FooterText $simple;
    New-LogEntry $stringBuilder $FooterLine $simple;
} Set-Alias -Name 'LOGF' -Value New-LogFooter -Option ReadOnly -Scope Global -Force;

function New-LogEntryConsole() {
    [cmdletbinding(DefaultParameterSetName = 'Single')]
    param(
        [Parameter(Position = 0, Mandatory)][System.Text.StringBuilder]$stringBuilder,
        [Parameter(Position = 1, Mandatory)][string]$dataToLog,
        [Parameter(Position = 2, Mandatory = $false, ParameterSetName = 'Single')]
        [Parameter(Position = 2, Mandatory, ParameterSetName = 'Multi')]
        [System.ConsoleColor]$lineColor,
        [Parameter(Position = 3, Mandatory, ParameterSetName = 'Multi', HelpMessage = "Color to highlight the delimited text.")]
        [System.ConsoleColor]$highlightColor,
        [Parameter(Position = 4, Mandatory = $false, ParameterSetName = 'Multi', HelpMessage = "Character to split the input dataToLog string on for highlighting. Defaults to pipe (|) character.")]
        [char]$delimiter = '|',
        [bool]$simple = $false
    )

    [void]$stringBuilder.AppendFormat('[{0}] : {1}', $(New-TimeStamp $simple), $dataToLog.Replace($delimiter, ' ').Trim()).AppendLine();

    Write-Host "[" -NoNewline; Write-Host $(New-TimeStamp $simple) -ForegroundColor White -NoNewline; Write-Host "] : " -NoNewline;

    if ($null -eq $lineColor) {
        Write-Host $dataToLog;
    } else {

        if ($null -eq $highlightColor) {
            Write-Host $dataToLog -ForegroundColor $lineColor;
        } else {

            $split = $dataToLog.Split($delimiter);
            $final = $split.Count - 1;

            for ($i = 0; $i -lt $split.Count; $i++) {
                if ($i % 2 -eq 0) {
                    if ($i -eq $final) {
                        Write-Host $(" {0}" -f $split[$i]) -ForegroundColor $lineColor;
                    } else {
                        if ($i -eq 0) {
                            Write-Host $("{0} " -f $split[$i]) -ForegroundColor $lineColor -NoNewline;
                        } else {
                            Write-Host $(" {0} " -f $split[$i]) -ForegroundColor $lineColor -NoNewline;
                        }
                    }
                } else {
                    if ($i -eq $final) {
                        Write-Host $split[$i] -ForegroundColor $highlightColor;
                    } else {
                        Write-Host $split[$i] -ForegroundColor $highlightColor -NoNewline;
                    }
                }
            }
        }
    }
}

function New-LogEntryConsoleHeader() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][System.Text.StringBuilder]$stringBuilder,
        [Parameter(Position = 1, Mandatory)][ValidateNotNullorEmpty()][string]$HeaderText,
        [Parameter(Position = 2, Mandatory = $false)]
        [System.ConsoleColor]$lineColor = [System.ConsoleColor]::White,
        [Parameter(Position = 3, Mandatory = $false)]
        [System.ConsoleColor]$highlightColor = [System.ConsoleColor]::Yellow,
        [Parameter(Position = 4, Mandatory = $false)]
        [System.ConsoleColor]$borderColor = [System.ConsoleColor]::Cyan,
        [bool]$simple = $false
    )

    New-LogEntryConsole $stringBuilder $HeaderLine $borderColor -simple $simple;
    New-LogEntryConsole $stringBuilder $(New-CenteredString $HeaderText -consoleLog) $lineColor $highlightColor -simple $simple;
    New-LogEntryConsole $stringBuilder $HeaderLine $borderColor -simple $simple;
}

function New-LogEntryConsoleFooter() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][System.Text.StringBuilder]$stringBuilder,
        [Parameter(Position = 1, Mandatory)][ValidateNotNullorEmpty()][string]$FooterText,
        [Parameter(Position = 2, Mandatory = $false)]
        [System.ConsoleColor]$lineColor = [System.ConsoleColor]::Gray,
        [Parameter(Position = 3, Mandatory = $false)]
        [System.ConsoleColor]$highlightColor = [System.ConsoleColor]::DarkGreen,
        [Parameter(Position = 4, Mandatory = $false)]
        [System.ConsoleColor]$borderColor = [System.ConsoleColor]::DarkGreen,
        [bool]$simple = $false
    )

    New-LogEntryConsole $stringBuilder $FooterLine $borderColor -simple $simple;
    New-LogEntryConsole $stringBuilder $(New-CenteredString $FooterText -consoleLog) $highlightColor $lineColor -simple $simple;
    New-LogEntryConsole $stringBuilder $FooterLine $borderColor -simple $simple;
}

function New-ConsoleLog() {
    [cmdletbinding(DefaultParameterSetName = 'Single')]
    param(
        [Parameter(Position = 0, Mandatory)][string]$dataToLog,
        [Parameter(Position = 1, Mandatory = $false, ParameterSetName = 'Single')]
        [Parameter(Position = 1, Mandatory, ParameterSetName = 'Multi')]
        [System.ConsoleColor]$lineColor,
        [Parameter(Position = 2, Mandatory, ParameterSetName = 'Multi', HelpMessage = "Color to highlight the delimited text.")]
        [System.ConsoleColor]$highlightColor,
        [Parameter(Position = 3, Mandatory = $false, ParameterSetName = 'Multi', HelpMessage = "Character to split the input dataToLog string on for highlighting. Defaults to pipe (|) character.")]
        [char]$delimiter = '|',
        [Parameter(Mandatory = $false)][string]$FilePath = $(if (-not([string]::IsNullOrEmpty($global:logFilePath))) { $global:logFilePath } else { $null }),
        [Parameter(Mandatory = $false)][bool]$simple = $true
    )

    Write-Host "[" -NoNewline; Write-Host $(New-TimeStamp -simple $simple ) -ForegroundColor White -NoNewline; Write-Host "] : " -NoNewline;

    if ($null -eq $lineColor) {
        Write-Host $dataToLog;
    } else {

        if ($null -eq $highlightColor) {
            Write-Host $dataToLog -ForegroundColor $lineColor;
        } else {

            $split = $dataToLog.Split($delimiter);
            $final = $split.Count - 1;

            for ($i = 0; $i -lt $split.Count; $i++) {
                if ($i % 2 -eq 0) {
                    if ($i -eq $final) {
                        Write-Host $(" {0}" -f $split[$i]) -ForegroundColor $lineColor;
                    } else {
                        if ($i -eq 0) {
                            Write-Host $("{0} " -f $split[$i]) -ForegroundColor $lineColor -NoNewline;
                        } else {
                            Write-Host $(" {0} " -f $split[$i]) -ForegroundColor $lineColor -NoNewline;
                        }
                    }
                } else {
                    if ($i -eq $final) {
                        Write-Host $split[$i] -ForegroundColor $highlightColor;
                    } else {
                        Write-Host $split[$i] -ForegroundColor $highlightColor -NoNewline;
                    }
                }
            }
        }
    }

    if (-not([string]::IsNullOrEmpty($FilePath))) {
        New-FileLogEntry $FilePath $dataToLog.Replace('|', ' ');
    }
} Set-Alias -Name 'LOGC' -Value New-ConsoleLog -Option ReadOnly -Scope Global -Force;

function New-ConsoleLogHeader() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$HeaderText,
        [Parameter(Position = 1, Mandatory = $false)]
        [System.ConsoleColor]$lineColor = [System.ConsoleColor]::White,
        [Parameter(Position = 2, Mandatory = $false)]
        [System.ConsoleColor]$highlightColor = [System.ConsoleColor]::Yellow,
        [Parameter(Position = 3, Mandatory = $false)]
        [System.ConsoleColor]$borderColor = [System.ConsoleColor]::Cyan
    )

    New-ConsoleLog $HeaderLine $borderColor;
    New-ConsoleLog $(New-CenteredString $HeaderText -consoleLog) $lineColor $highlightColor;
    New-ConsoleLog $HeaderLine $borderColor;
} Set-Alias -Name 'LOGCH' -Value New-ConsoleLogHeader -Option ReadOnly -Scope Global -Force;

function New-ConsoleLogFooter() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$FooterText,
        [Parameter(Position = 1, Mandatory = $false)]
        [System.ConsoleColor]$lineColor = [System.ConsoleColor]::Gray,
        [Parameter(Position = 2, Mandatory = $false)]
        [System.ConsoleColor]$highlightColor = [System.ConsoleColor]::DarkGreen,
        [Parameter(Position = 3, Mandatory = $false)]
        [System.ConsoleColor]$borderColor = [System.ConsoleColor]::DarkGreen
    )

    New-ConsoleLog $FooterLine $borderColor;
    New-ConsoleLog $(New-CenteredString $FooterText -consoleLog) $highlightColor $lineColor;
    New-ConsoleLog $FooterLine $borderColor;
} Set-Alias -Name 'LOGCF' -Value New-ConsoleLogFooter -Option ReadOnly -Scope Global -Force;

function New-FileLogEntry() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][string]$LogFileFullPath,
        [Parameter(Position = 1, Mandatory)][string]$dataToLog
    )

    Out-File -FilePath "$LogFileFullPath" -Append -InputObject $('[{0}] : {1}' -f $(New-TimeStamp -simple $simple), $dataToLog.Trim())
} Set-Alias -Name 'FLOG' -Value New-FileLogEntry -Option ReadOnly -Scope Global -Force;

function New-CenteredString {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$stringToCenter,
        [Parameter(Position = 1, Mandatory = $false)]
        [switch]$consoleLog
    )

    if ($consoleLog) {
        return $("{0}{1}" -f (' ' * (([Math]::Max(0, 98 / 2) - ([Math]::Floor(($stringToCenter.Length) / 2))))), $stringToCenter);
    } else {
        return $("{0}{1}" -f (' ' * (([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Floor($stringToCenter.Length / 2)))), $stringToCenter);
    }
} Set-Alias -Name 'CENTER' -Value New-CenteredString -Option ReadOnly -Scope Global -Force;

function New-InsertString() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][AllowNull()]
        [Object[]]$InputArray,
        [switch]$CleanStrings,
        [switch]$LocalTime
    )

    $StringBuilder = [System.Text.StringBuilder]::new();
    [void]$StringBuilder.Append("(");

    $i = 0;

    foreach ($item in $InputArray) {

        if ($i -gt 0) {
            [void]$StringBuilder.Append(", ");
        }

        if ($null -eq $item) {
            [void]$StringBuilder.Append("''");
        } else {
            switch -Regex ($item.GetType().Name) {

                'String' {
                    if ($item -eq 'NOW()') {
                        [void]$StringBuilder.AppendFormat("{0}", $item);
                    } else {
                        if ($CleanStrings.IsPresent -and -not([string]::IsNullOrWhiteSpace($item))) {
                            [void]$StringBuilder.AppendFormat("'{0}'", $(Get-CleanString $item));
                        } else {
                            [void]$StringBuilder.AppendFormat("'{0}'", $item);
                        }
                    }
                }
                'Int|Decimal|Double|Single|Float' {
                    [void]$StringBuilder.AppendFormat("{0}", $item);
                }
                'Boolean' {
                    [void]$StringBuilder.AppendFormat("{0}", [int]$item)
                }
                'DateTime' {
                    if ($LocalTime.IsPresent) {
                        [void]$StringBuilder.AppendFormat("'{0}'", $(Get-SQLDateTime $item));
                    } else {
                        [void]$StringBuilder.AppendFormat("'{0}'", $(Get-LocalTime $item));
                    }
                }
                default {
                    [void]$StringBuilder.AppendFormat("'{0}'", $item);
                }
            }
        }

        $i++;
    }

    [void]$StringBuilder.Append(")");

    return $StringBuilder.ToString();
} Set-Alias -Name 'INSSTR' -Value New-InsertString -Option ReadOnly -Scope Global -Force;

function New-PaddedString() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$stringToPad,
        [Parameter(Position = 1, Mandatory)]
        [ValidateNotNullorEmpty()]
        [int]$padLength,
        [Parameter(Position = 2, Mandatory = $false)]
        [char]$padChar,
        [Parameter(Position = 3, Mandatory = $false)]
        [switch]$padLeft = $false
    )

    if ($padLeft) {
        if (-not($null -eq $padChar)) {
            $paddedString = $stringToPad.PadLeft($padLength, $padChar);
        } else {
            $paddedString = $stringToPad.PadLeft($padLength);
        }
    } else {
        if (-not($null -eq $padChar)) {
            $paddedString = $stringToPad.PadRight($padLength, $padChar);
        } else {
            $paddedString = $stringToPad.PadRight($padLength);
        }
    }

    return $paddedString;
} Set-Alias -Name 'PAD' -Value New-PaddedString -Option ReadOnly -Scope Global -Force;

function New-TimeStamp {
    param(
        [Parameter(Position = 0)]
        [bool]$simple = $false,
        [switch]$fileName
    )

    if ($simple) {
        if ($fileName.IsPresent) {
            return '{0:yyyy.MM.dd HH.mm.ss}' -f (Get-Date);
        } else {
            return '{0:yyyy.MM.dd HH:mm:ss}' -f (Get-Date);
        }
    } else {
        if ($fileName.IsPresent) {
            return '{0:yyyy.MM.dd HH.mm.ss.ffff}' -f (Get-Date);
        } else {
            return '{0:yyyy.MM.dd HH:mm:ss:ffff}' -f (Get-Date);
        }
    }
} Set-Alias -Name 'TS' -Value New-TimeStamp -Option ReadOnly -Scope Global -Force;

function New-InputPrompt() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory = $false, HelpMessage = "Text for First Button")]
        [string]$FirstButton = '&Yes',
        [Parameter(Position = 1, Mandatory = $false, HelpMessage = "Text for Second Button")]
        [string]$SecondButton = '&No',
        [Parameter(Position = 2, Mandatory = $false, HelpMessage = "Descriptive text for First Button mouseover.")]
        [string]$FirstDesc = '',
        [Parameter(Position = 3, Mandatory = $false, HelpMessage = "Descriptive text for Second Button mouseover.")]
        [string]$SecondDesc = '',
        [Parameter(Position = 4, Mandatory, HelpMessage = "Message box title.")]
        [string]$Title,
        [Parameter(Position = 5, Mandatory, HelpMessage = "Message box text.")]
        [string]$Message
    )

    $BTN1 = New-Object System.Management.Automation.Host.ChoiceDescription $FirstButton, $FirstDesc;
    $BTN2 = New-Object System.Management.Automation.Host.ChoiceDescription $SecondButton, $SecondDesc;

    $OPT = [System.Management.Automation.Host.ChoiceDescription[]]($BTN1, $BTN2)

    return $host.ui.PromptForChoice($Title, $Message, $OPT, 1);
}

function New-MessageBox() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][string]$Title,
        [Parameter(Position = 1, Mandatory)][string]$Message,
        [Parameter(Position = 2, Mandatory)][System.Windows.MessageBoxButton]$ButtonType,
        [Parameter(Position = 3, Mandatory)][System.Windows.MessageBoxImage]$MessageBoxImage
    )

    Import-Presentation;

    $Result = [System.Windows.MessageBox]::Show($Message, $Title, $ButtonType, $MessageBoxImage);
    Write-Verbose "MessageBox response: $Result";

    return $Result;
}

function New-InputBox() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][string]$Title,
        [Parameter(Position = 1, Mandatory)][string]$Message
    )

    Import-VisualBasic;

    $Result = [Microsoft.VisualBasic.Interaction]::InputBox($Message, $Title);
    Write-Verbose "MessageBox response: $Result";

    return $Result;
}

function New-IGatherDataSet() {
    return [IGatherDataSet]::New();
}

function New-IGatherEntry() {
    return [IGatherEntry]::New();
}

function New-IGatherDataSet() {
    param(
        [int]$ComputerID,
        [string]$Category
    )

    $DataSet = [IGatherDataSet]::New();
    $DataSet.ComputerID = $ComputerID;
    $DataSet.Category = $Category;

    return $DataSet;
}

function New-IGatherEntry() {
    param(
        [string] $Table,
        [string] $InsertString,
        [string] $DeleteFilter
    )

    $Entry = [IGatherEntry]::New();
    $Entry.Table = $Table;
    $Entry.InsertString = $InsertString;
    $Entry.DeleteFilter = $DeleteFilter;

    return $Entry;
}

function New-IGatherEntry() {
    param(
        [string] $Table,
        [string] $InsertString,
        [string] $DeleteFilter,
        [string] $DuplicateString
    )

    $Entry = [IGatherEntry]::New();
    $Entry.Table = $Table;
    $Entry.InsertString = $InsertString;
    $Entry.DeleteFilter = $DeleteFilter;
    $Entry.DuplicateString = $DuplicateString;

    return $Entry;
}

#endregion New

#region Set

function Set-RegistryProperty() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][string]$RegistryPath,
        [Parameter(Position = 1, Mandatory)][string]$PropertyName,
        [Parameter(Position = 2, Mandatory)][AllowEmptyString()][string]$PropertyValue,
        [Parameter(Position = 4, Mandatory = $false)][bool]$IsString = $false,
        [Parameter(Position = 4, Mandatory)][System.Text.StringBuilder]$sbError,
        [Parameter(Position = 5, Mandatory)][System.Text.StringBuilder]$sbSuccess
    )

    if (-not(Test-Path $RegistryPath -ErrorAction SilentlyContinue)) {

        try {

            $null = New-Item $RegistryPath -Force;
            New-LogEntry $sbSuccess "Added registry key: $RegistryPath";

        } catch {
            New-LogEntry $sbError "Failed to add registry key $($RegistryPath): $($_.Exception.Message)";
            return;
        }
    }

    try {
        if (-not(Get-ItemProperty -Path $RegistryPath -Name $PropertyName -ErrorAction SilentlyContinue)) {

            if ($IsString) {
                $null = New-ItemProperty -Path $RegistryPath -Name $PropertyName -Value $PropertyValue -PropertyType String -Force;
            } else {
                $null = New-ItemProperty -Path $RegistryPath -Name $PropertyName -Value $PropertyValue -PropertyType DWord -Force;
            }

            New-LogEntry $sbSuccess "Added $RegistryPath property $PropertyName and set value to $PropertyValue";

        } else {

            $CurrentValue = Get-ItemPropertyValue -Path $RegistryPath -Name $PropertyName -ErrorAction SilentlyContinue;

            if ($CurrentValue -ne $PropertyValue) {

                Write-Verbose "$RegistryPath property $PropertyName | CURRENT : $CurrentValue | EXPECTED: $PropertyValue";

                $null = Set-ItemProperty -Path $RegistryPath -Name $PropertyName -Value $PropertyValue -Force;
                New-LogEntry $sbSuccess "Set $RegistryPath property $PropertyName value to $PropertyValue";
            }
        }
    } catch {
        New-LogEntry $sbError "Failed to Add/Set $RegistryPath property $PropertyName to $($PropertyValue): $($_.Exception.Message)";
    }
}

#endregion Set

#region Remove

function Remove-RegistryProperty() {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)][string]$RegistryPath,
        [Parameter(Position = 1, Mandatory)][string]$PropertyName,
        [Parameter(Position = 2, Mandatory)][System.Text.StringBuilder]$sbError,
        [Parameter(Position = 3, Mandatory)][System.Text.StringBuilder]$sbSuccess
    )

    try {

        if (Get-ItemProperty -Path $RegistryPath -Name $PropertyName -ErrorAction SilentlyContinue) {

            Remove-ItemProperty -Path $RegistryPath -Name $PropertyName -ErrorAction Stop;
            New-LogEntry $sbSuccess "Removed registry property $PropertyName from key $RegistryPath.";
        }

    } catch {
        New-LogEntry $sbError "Failed to remove property $PropertyName from key $($RegistryPath): $($_.Exception.Message)";
    }
}

#endregion Remove

#region Test

function Test-PasswordComplexity($newComplexPswd) {
    Process {
        $criteriaMet = 0

        # Upper Case Characters (A through Z, with diacritic marks, Greek and Cyrillic characters)
        if ($newComplexPswd -cmatch '[A-Z]') {
            $criteriaMet++
        }

        # Lower Case Characters (a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters)
        if ($newComplexPswd -cmatch '[a-z]') {
            $criteriaMet++
        }

        # Numeric Characters (0 through 9)
        if ($newComplexPswd -match '\d') {
            $criteriaMet++
        }

        # Special Chracters (Non-alphanumeric characters, currency symbols such as the Euro or British Pound are not counted as special characters for this policy setting)
        if ($newComplexPswd -match '[\^~!@#$%^&*_+=`|\\(){}\[\]:;"''<>,.?/]') {
            $criteriaMet++
        }

        # Check if It Matches Default Windows Complexity Requirements
        if ($criteriaMet -lt 3) {
            return $false
        }
        if ($newComplexPswd.Length -lt 8) {
            return $false
        }
        return $true
    }
}

#endregion Test

#region Write

function Write-HostCenter {
    [cmdletbinding()]
    param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$Message
    )

    Write-Host ("{0}{1}" -f (' ' * (([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Floor($Message.Length / 2)))), $Message);

} Set-Alias -Name Write-Center -Value Write-HostCenter -Option ReadOnly -Scope Global -Force;

#endregion Write

<# Borrowed from https://github.com/LabtechConsulting/LabTech-Powershell-Module #>

If (($MyInvocation.Line -match 'Import-Module' -or $MyInvocation.MyCommand -match 'Import-Module') -and -not ($MyInvocation.Line -match $ModuleGuid -or $MyInvocation.MyCommand -match $ModuleGuid)) {

    Import-Presentation;
    Import-VisualBasic;

    # Only export module members when being loaded as a module
    Export-ModuleMember -Function * -Alias * -Variable @('FooterLine', 'HeaderLine', 'IGatherDataSet', 'CWADataSet', 'Symbols') -EA 0 -WA 0;
}