InstallFastLinqPowerKit.psm1

# Script level parameters
Param (
    [Switch] $ConfigureWinRM = $false,              # For silent WinRM configuration
    [Switch] $InstallPoshSSHModule = $false,
    [Switch] $InstallRESTServer = $false,           # For (mostly) silent REST server setup
    [Switch] $InstallRESTServerOnly = $false,
    [PSCredential] $Credential = $null,
    [String] [ValidateSet($null, 'http', 'https', 'HTTP', 'HTTPS', 'both')] $RESTServerProtocol = 'http',
    [Int32] [ValidateRange(1, 65535)] $RESTServerPort = 7777,
    [ValidateSet($null, $true, $false)] $CreateSelfSignedHTTPSCert =$true,
    [SecureString] $HTTPSCertPassword = $null
)

# Get the number of Script parameters
$NumberOfArgs = $PSBoundParameters.Count

# Helper Functions

Function Out-Log {
    # This function controls console output, displaying it only if the Verbose variable is set.
    # This function now has another parameter, Level. Commands that may output messages now have a level parameter set. Key is below.
    # Level = 0 - Completely silent except for success / failure messages.
    # Level = 1 - Default if $Script:verbose is not set, about 5 output messages per added appx.
    # Level = 2 - Level 1 + Appcmd output messages, Add-AppxPackage messages
    # Level = 3 - Level 2 + Registry, XML, enabling of features, WinRM, setx, IO, netsh, etc. Very verbose.
    Param (
        [Parameter(Mandatory=$true)] [AllowNull()] $Data,
        [Parameter(Mandatory=$true)] [AllowNull()] $Level,
        [Parameter(Mandatory=$false)] [AllowNull()] $ForegroundColor,
        [Parameter(Mandatory=$false)] [AllowNull()] [Switch] $NoNewline
    )
    if ($Script:Verbose -eq $null) { $Script:Verbose = 1 }
    if ($Data -eq $null) { return }
    $Data = ($Data | Out-String)
    $Data = $Data.Substring(0, $Data.Length - 2)  # Remove extra `n from Out-String (only one)
    $Data = $Data.Replace('"', '`"')
    $expression = "Write-Host -Object `"$Data`""
    if ($ForegroundColor -ne $null) { $expression += " -ForegroundColor $ForegroundColor" }
    if ($NoNewline) { $expression += " -NoNewline" }
    if ($Level -le $Script:verbose) {
        Invoke-Expression $expression
    }
}

Function Test-ExecutionPolicy {
    # This function checks to make sure the ExecutionPolicy is not Unrestricted and if it is, prompts the user to change it to either RemoteSigned or Bypass.
    # If the ExecutionPolicy is left at Unrestricted, the user will receive one prompt for every cmdlet each time they open a new PowerShell window.
    $currentExecutionPolicy = Get-ExecutionPolicy
    if ($currentExecutionPolicy -eq 'Unrestricted') {

        $title = 'Execution Policy Configuration'
        $message = "Your current execution policy is $currentExecutionPolicy. This policy causes PowerShell to prompt you every session for every cmdlet. Set the execution policy to RemoteSigned or Bypass to avoid this."
        $remoteSigned = New-Object System.Management.Automation.Host.ChoiceDescription '&RemoteSigned', `
            'Sets the execution policy to RemoteSigned.'
        $bypass = New-Object System.Management.Automation.Host.ChoiceDescription '&Bypass', `
            'Sets the execution policy to Bypass.'
        $ignoreAndContinue = New-Object System.Management.Automation.Host.ChoiceDescription '&Ignore and Continue', `
            'Does not change the execution policy.'
        $options = [System.Management.Automation.Host.ChoiceDescription[]]($remoteSigned, $bypass, $ignoreAndContinue)
        $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0)


        if ($tempResult -eq 0) { $newExecutionPolicy = 'RemoteSigned' }
        if ($tempResult -eq 1) { $newExecutionPolicy = 'Bypass' }
        if ($tempResult -eq 2) { return }

        # If the user has set a CurrentUser sceope level ExecutionPolicy, then change this to RemoteSigned/Bypass as well.
        if ((Get-ExecutionPolicy -Scope CurrentUser) -ne 'Undefined') {
            Set-ExecutionPolicy -ExecutionPolicy $newExecutionPolicy -Scope CurrentUser -Force
        }
        Set-ExecutionPolicy -ExecutionPolicy $newExecutionPolicy -Scope LocalMachine -Force

        Out-Log -Data "ExecutionPolicy changed from $currentExecutionPolicy to $(Get-ExecutionPolicy)." -Level 0
    }
}

Function Test-IfNano {
    # Check if OS is Nano or Non-Nano
    $envInfo = [Environment]::OSVersion.Version
    $envInfoCimInst = Get-CimInstance Win32_OperatingSystem
    return ( ($envInfo.Major -eq 10) -and ($envInfo.Minor -eq 0) -and ($envInfoCimInst.ProductType -eq 3) -and (($envInfoCimInst.OperatingSystemSKU -eq 143) -or ($envInfoCimInst.OperatingSystemSKU -eq 144) ) )
}

Function Test-IfServerCore {
    # Check if OS is Server Core
    $regKey = 'hklm:/software/microsoft/windows nt/currentversion'
    return (Get-ItemProperty $regKey).InstallationType -eq 'Server Core'
}

Function Get-CmdletsAppxFileName {
    if ($Script:isNano) { return '.\FastLinqProvider-WindowsServerNano.appx' }
    else { return '.\FastLinqProvider-WindowsServer.appx' }
}

Function Add-ManyToRegistryWriteOver {
    # This function changes multiple registry values at once.
    # Path is the path to the registry key to change, e.g. "HKLM:\Software\Classes\CLSID\{BC33E6C0-DB6A-4781-B924-CB1CDA88E4A1}\InprocServer32"
    # NameTypeValueArray is an array of arrays of size 3 containing Name, Type, and Value for the registry entry, e.g.
        # @(
            # @('(Default)', 'String', "C:\Program Files\WindowsApps\QLGCProvider_1.0.0.5_x64_NorthAmerica_yym61f5wjvd3m\qlgcProvider.dll"),
            # @('ThreadingModel', 'String', 'Free')
        # )

    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $NameTypeValueArray
    )
    # if path doesn't exist, create it
    if (-not (Test-Path -LiteralPath $Path)) {
        Out-Log -Data (New-Item -Path $Path -Force 2>&1) -Level 3
    }

    $arr = $NameTypeValueArray
    if ($arr[0] -isnot [System.Array]) {
        Out-Log -Data (New-ItemProperty -LiteralPath $Path -Name $arr[0] -Type $arr[1] -Value $arr[2] -Force 2>&1) -Level 3
        return
    }
    foreach ($ntv in $arr) {
        Out-Log -Data (New-ItemProperty -LiteralPath $Path -Name $ntv[0] -Type $ntv[1] -Value $ntv[2] -Force 2>&1) -Level 3
    }
}

Function Test-RegistryValue {
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Value
    )

    try {
        Out-Log -Data (Get-ItemProperty -Path $Path | Select-Object -ExpandProperty $Value -ErrorAction Stop 2>&1) -Level 3
        return $true
    } catch {
        return $false
    }
}

Function ConvertFrom-SecureToPlain {
    Param( [Parameter(Mandatory=$true)] [System.Security.SecureString] $SecurePassword )
    $passwordPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
    $plainTextPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto($passwordPointer)
    [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($passwordPointer)
    return $plainTextPassword
}

Function Convert-CredentialToHeaderAuthorizationString {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Credential )
    return [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($Credential.UserName)`:$(ConvertFrom-SecureToPlain -SecurePassword $Credential.Password)"))
}

Function Get-AppxPackageWrapper {
    # This function tries the Get-AppxPackage command several times before really failing.
    # This is necessary to prevent some intermittent issues with Get-AppxPackage.
    # This may no longer be necessary as it seems to only happen after an Add-AppxProvisionedPackage which is no longer used.
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxName )

    $numAttempts = 3
    $numSecondsBetweenAttempts = 5
    for ($i=0; $i -lt $numAttempts; $i++) {
        $appxPkg = Get-AppxPackage | Where-Object { $_.name -eq $AppxName }
        if ($appxPkg -ne $null) { return $appxPkg }
        Out-Log -Data ("Couldn't find Appx package. Trying again in $numSecondsBetweenAttempts seconds (attempt $($i+1) of $numAttempts) ...") -ForegroundColor DarkRed -Level 1
        Start-Sleep -Seconds $numSecondsBetweenAttempts
    }
    Out-Log -Data 'Failed to find Appx package. Please try running this script again.' -ForegroundColor Red -Level 0
    Exit
}

Function Invoke-IOWrapper {
    # This function tries the Expression several times before really failing.
    # This is necessary to prevent some intermittent issues with certain IO commands, especially Copy-Item.
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Expression,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SuccessMessage,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $FailMessage
    )

    $numAttempts = 5
    $numSecondsBetweenAttempts = 1
    $Expression += ' -ErrorAction Stop'
    for ($i=0; $i -lt $numAttempts; $i++) {
        try {
            Invoke-Expression $Expression
            if ($i -ne 0) { Out-Log -Data 'Success!' -ForegroundColor DarkGreen -Level 3 }
            Out-Log -Data $SuccessMessage -Level 1
            return
        } catch {
            if ($i -ne 0) { Out-Log -Data 'Failed.' -ForegroundColor DarkRed -Level 3 }
            Out-Log -Data "$FailMessage Trying again in $numSecondsBetweenAttempts seconds (attempt $($i+1) of $numAttempts) ... " -ForegroundColor DarkRed -NoNewline -Level 3
            Start-Sleep -Seconds $numSecondsBetweenAttempts
            $Global:err = $_
        }
    }
    Out-Log -Data 'Failed.' -ForegroundColor DarkRed -Level 3
    Out-Log -Data "$FailMessage Failed after $numAttempts attempts with $numSecondsBetweenAttempts second(s) between each attempt." -ForegroundColor Red -Level 0
    Out-Log -Data "Failed to install $Script:CurrentInstallation." -ForegroundColor Red -Level 0
    Exit
}


# Prompts
Function Set-WinRMConfigurationPrompt {
    if ($Script:isNano) { return $false } # Skip if Nano
    # if ($ConfigureWinRM) { return $true }

    $title = 'WinRM Configuration'
    $message = 'Do you want to configure WinRM in order to connect to Linux machines remotely?'
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', `
        'Configures WinRM to allow remote connections to Linux machines.'
    $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No', `
        'Does not configure WinRM. Remote connection to Linux machines will not be possible.'
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
    $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0)
    if ($tempResult -eq 0) { return $true } else { return $false }
}

Function Get-CredentialWrapper {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AddAccountToIISResponse )
    if (-not $AddAccountToIISResponse) { return $null }

    try {
        return Get-Credential -Message 'Enter the credentials for the account you would like to be added to IIS' -UserName $env:USERNAME
    } catch {
        Out-Log -Data 'No credentials supplied; no account will be added to IIS.' -Level 1
    }
}

Function Install-PoshSSHModulePrompt {
    if ($Script:isNano) { return $false } # Skip if Nano
    # if ($InstallPoshSSHModule) { return $true }

    $title = 'Install-Posh-SSH_Module'
    $message = 'Do you want to install Posh-SSH_Module in-order to manage [Linux and VMware_ESXi Remote Host Systems] or not ?'
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', 'install Posh-SSH_Module.'
    $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No', 'Does not install Posh-SSH_Module.'
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

    $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0)
    if ($tempResult -eq 0) { return $true } else { return $false }
}

Function Install-RESTServerPrompt {
    if ($Script:isNano) { return $false } # Skip if Nano
    # if ($InstallRESTServer) { return $true }

    $title = 'Install-REST Server'
    $message = 'Do you want to install the REST server to be able to invoke PowerShell cmdlets via REST?'
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', 'Installs the REST server.'
    $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No', 'Does not install the REST server.'
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

    $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0)
    if ($tempResult -eq 0) { return $true } else { return $false }
}

Function Get-RESTServerProtocolPrompt {
    $defaultValue = 'http'
    while ($true) {
        $response = Read-Host -Prompt "Would you like to use http or https? [$defaultValue] ?"
        if ([string]::IsNullOrWhiteSpace($response)) {
            return $defaultValue
        }
        elseif ($response -ne 'http' -and $response -ne 'https' -and $response -ne 'both') {
            Out-Log -Data "Response must be either: 'http' or 'https'; please try again.`n" -ForegroundColor Red -Level 0
        }
        else {
            return $response
        }
    }
}

Function Get-RESTServerPortPrompt {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Protocol )
    $defaultValue = '7777'
    if ($Protocol -eq 'https') {
        $defaultValue = '7778'
    }
    while ($true) {
        $response = Read-Host -Prompt "What $($Protocol.ToUpper()) port would you like to use for the REST server [$defaultValue] ?"
        if ([string]::IsNullOrWhiteSpace($response)) {
            return $defaultValue
        }
        $responseAsInt = $response -as [int]
        if ($responseAsInt -eq $null -or $responseAsInt -lt 1 -or $responseAsInt -gt 65535) {
            Out-Log -Data "Response must be a valid port number; please try again.`n" -ForegroundColor Red -Level 0
        }
        else {
            return $response
        }
    }
    return $response
}

Function Get-RESTServerExistingHTTPSCert {
    $title = 'REST Server HTTPS Certificate Preference'
    $message = "Would you like to create a self-signed HTTPS certificate?`nSelecting no will allow you to choose the location of your existing HTTPS certificate."
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', 'Creates a self-signed HTTPS certificate.'
    $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No', 'Opens file selection dialog for location of existing HTTPS certificate.'
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

    while ($true) {
        $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0)
        if ($tempResult -eq 0) {
            return $null  # create self-signed cert
        }

        $fileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{
            Title = 'Select HTTPS certificate to use for the REST server'
            InitialDirectory = (Get-Location).Path
            Filter = 'Certificate File (*.pfx)|*.pfx'
            Multiselect = $false
        }
        $null = $fileBrowser.ShowDialog()

        if (-not [string]::IsNullOrWhiteSpace($fileBrowser.FileName)) {
            return $fileBrowser.FileName
        }
    }
}

Function Get-RESTServerHTTPSCertPasswordPrompt {
    while ($true) {
        $password1 = Read-Host -AsSecureString 'Please enter a password for the HTTPS certificate'
        $password2 = Read-Host -AsSecureString 'Please re-enter the password for the HTTPS certificate'
        $password1Text = ConvertFrom-SecureToPlain -SecurePassword $password1
        $password2Text = ConvertFrom-SecureToPlain -SecurePassword $password2
        if ([string]::IsNullOrWhiteSpace($password1Text) -or [string]::IsNullOrWhiteSpace($password2Text)) {
            Out-Log -Data "One or both passwords are empty; please try again.`n" -ForegroundColor Red -Level 0
        }
        elseif ($password1Text -ne $password2Text) {
            Out-Log -Data "Passwords do not match; please try again.`n" -ForegroundColor Red -Level 0
        }
        else {
            return $password1
        }
    }
}

Function Get-RESTServerHTTPSCertPasswordPromptSimple {
    $password = Read-Host -AsSecureString 'Please enter the password for the HTTPS certificate'
    return $password
}

Function Test-HTTPSCertPassword {
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $CertificateFilePath,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SecurePassword
    )
    $passwordText = ConvertFrom-SecureToPlain -SecurePassword $SecurePassword
    $null = certutil.exe -p $passwordText -dump $CertificateFilePath 2>&1
    return $LASTEXITCODE -eq 0
}

Function Test-LastExitCode {
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $ExitCode,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Message,
        [Parameter(Mandatory=$false)] [AllowNull()] [Switch] $ExitScriptIfBadErrorCode
    )
    if ($ExitCode -eq 0) { return; }

    if (-not $ExitScriptIfBadErrorCode) {
        Out-Log -Data "WARNING: $Message" -ForegroundColor Yellow -Level 1
    } else {
        Out-Log -Data "ERROR: $Message" -ForegroundColor Red -Level 1
        Exit
    }
}

# Main Functions

Function Add-AppxTrustedAppsRegistryKey {
    # This function remembers user's current AllowAllTrustedApps registry setting and then changes it to
    # allow installation of all trusted apps.
    if ($Script:isNano) { return } # Skip if Nano
    if ($Script:isServerCore) { return } # Skip if Server Core

    # Remember user's registry setting before modify it to be able to set it back later.
    $appxPolicyRegKey = 'HKLM:\Software\Policies\Microsoft\Windows\Appx'
    $appxPolicyRegValName = 'AllowAllTrustedApps'
    if (Test-Path -Path $appxPolicyRegKey) {
        $appxPolicyRegValData = (Get-ItemProperty -Path $appxPolicyRegKey).$appxPolicyRegValName
        if ($appxPolicyRegValData -ne $null) {
            $Script:savedAppxPolicyRegValData = $appxPolicyRegValData
        }
        Start-Sleep -Milliseconds 1000  # Otherwise access denied error when do Add-ManyToRegistryWriteOver below
    }

    Add-ManyToRegistryWriteOver -Path $appxPolicyRegKey -NameTypeValueArray @(
        @($appxPolicyRegValName, 'DWord', 1) )
}

Function Remove-CLSIDRegistryPaths {
    # if (-not $Script:isNano) { return } # Skip if NOT Nano
    if (Test-Path -Path "Registry::HKCR\CLSID\$Script:CLSID") {
        Remove-Item -Path "Registry::HKCR\CLSID\$Script:CLSID" -Recurse
    }
    if (Test-Path -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID") {
        Remove-Item -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID" -Recurse
    }
}

Function Install-ForServerCore {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxFileName )

    $serverCoreInstallPath = "$env:ProgramFiles\QLogic Corporation\PowerKit"
    $appxFileNameSimple = $AppxFileName.Replace('.\', '')
    $appxZipFileName = $appxFileNameSimple.Replace('.appx', '.zip')

    # Create installation directory if doesn't exist and copy Appx file to it (renamed to .zip)
    if (-not (Test-Path $serverCoreInstallPath)) {
        New-Item -Path $serverCoreInstallPath -Type Directory
        Copy-Item -Path $AppxFileName -Destination "$serverCoreInstallPath\$appxZipFileName"
    }

    # Unzip appx file to PowerKit directory
    Expand-Archive -Path "$serverCoreInstallPath\$appxZipFileName" -DestinationPath $serverCoreInstallPath

    # Register CIMProvider
    Register-CimProvider.exe -Namespace Root/qlgcfastlinq -ProviderName QLGCProvider -Path "$serverCoreInstallPath\QLGCProvider.dll" -verbose -ForceUpdate -HostingModel LocalSystemHost
}

Function Add-AppxPackageWrapper {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxFileName )

    $savedProgressPreference = $Global:ProgressPreference
    $Global:ProgressPreference = 'SilentlyContinue'
    Out-Log -Data (Add-AppxPackage $AppxFileName 2>&1) -Level 2
    $Global:ProgressPreference = $savedProgressPreference
    Out-Log -Data "Added '$($AppxFileName.Replace('.\', ''))' AppxPackage." -Level 1
}

Function Add-CLSIDRegistryPathsAndMofCompInstall {
    # if ($Script:isNano) { return } # Skip if Nano
    # Perform steps of Register-CimProvider.exe -Namespace Root/qlgcfastlinq -ProviderName QLGCProvider -Path QLGCProvider.dll -verbose -ForceUpdate -HostingModel LocalSystemHost
    $appxPath = (Get-AppxPackageWrapper -AppxName 'QLGCProvider').InstallLocation
    $mofcompOutput = & mofcomp.exe -N:root\qlgcfastlinq $appxPath\QLGCProvider.mof 2>&1
    if ($mofcompOutput -match 'Error') {
        Out-Log -Data "Failed to register '$appxPath\QLGCProvider.mof': $($mofcompOutput -match 'Error')" -ForegroundColor Red -Level 1
    }

    Add-ManyToRegistryWriteOver -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID" -NameTypeValueArray @(
        @('(Default)', 'String', 'QLGCProvider') )
    Add-ManyToRegistryWriteOver -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID\InprocServer32" -NameTypeValueArray @(
        @('(Default)', 'String', "$appxPath\qlgcProvider.dll"),
        @('ThreadingModel', 'String', 'Free') )
}

Function Remove-AppxTrustedAppsRegistryKey {
    if ($Script:isNano) { return } # Skip if Nano
    if ($Script:isServerCore) { return } # Skip if Server Core

    $appxPolicyRegKey = 'HKLM:\Software\Policies\Microsoft\Windows\Appx'
    $appxPolicyRegValName = 'AllowAllTrustedApps'

    # If user previously had this registry key, set it back to what it was before. Otherwise, just delete the registry key.
    if ($Script:savedAppxPolicyRegValData -ne $null) {
        Add-ManyToRegistryWriteOver -Path $appxPolicyRegKey -NameTypeValueArray @(
            @($appxPolicyRegValName, 'DWord', $Script:savedAppxPolicyRegValData) )
    } else {
        Remove-ItemProperty -Path $appxPolicyRegKey -Name $appxPolicyRegValName
    }
}

Function Copy-CmdletsToProgramData {
    # Check for ProgramData directories with old versions and remove them
    $oldPaths = (Get-ChildItem $env:ProgramData | Where-Object { ($_.Name).StartsWith('QLGCProvider_') }).FullName
    foreach ($oldPath in $oldPaths) {
        Remove-Item $oldPath -Recurse -Force
    }

    # Copy cmdlets to ProgramData
    $appxPkg = Get-AppxPackageWrapper -AppxName 'QLGCProvider'
    $cmdletsPath = $appxPkg.InstallLocation + '\Cmdlets\'
    if (Test-Path $cmdletsPath) {
        $cmdletsDestPath = "$env:ProgramData\$($appxPkg.PackageFullName)"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$cmdletsPath`" -Destination $cmdletsDestPath -Recurse -Force" `
            -SuccessMessage "Cmdlets copied to '$cmdletsDestPath'." `
            -FailMessage "Failed to copy cmdlets."
    }
}

Function Copy-XMLTemplatesToProgramData {
    # Copy cmdlets to ProgramData
    $appxPkg = Get-AppxPackageWrapper -AppxName 'QLGCProvider'
    $xmlsTemplatesPath = $appxPkg.InstallLocation + '\Cmdlets\XML'
    if (Test-Path $xmlsTemplatesPath) {
        $xmlsTemplatesDestPath = "C:\Program Files\Marvell\PowerKit\XML"
        if (Test-Path $xmlsTemplatesDestPath){
            Remove-Item $xmlsTemplatesDestPath -Recurse -Force
        }
        
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$xmlsTemplatesPath`" -Destination `"$xmlsTemplatesDestPath`" -Recurse -Force" `
            -SuccessMessage "XML Templates copied to '$xmlsTemplatesDestPath'." `
            -FailMessage "Failed to copy XML Templates."
    }
}

Function Add-CmdletsPathToPSModulePath {
    # This function adds cmdlet module path to system PSModulePath environment variable permanently.

    if ($Script:isServerCore) {
        $appxCmdletsPath = "$env:ProgramFiles\QLogic Corporation\PowerKit\Cmdlets"
    } else {
        $appxCmdletsPath = (Get-AppxPackageWrapper -AppxName 'QLGCProvider').InstallLocation + '\Cmdlets'
    }

    if (Test-RegistryValue -Path 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment' -Value 'PSModulePath') {
        $currPSModulePathArr = ((Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment').PSModulePath).Split(';')
        $newPSModulePathArr = @()
        # Remove any old paths with QLGCProvider from PSModulePath
        foreach ($path in $currPSModulePathArr) {
            if (-not $path.Contains('QLGCProvider')) {
                $newPSModulePathArr += $path
            }
        }
        # Add new cmdlet path to PSModulePath
        $newPSModulePathArr += $appxCmdletsPath  # Skip for uninstall script
        $newPSModulePathStr = $newPSModulePathArr -join ';'

        # Write PSModulePath to registry and set local session variable
        Out-Log -Data (& setx /s $env:COMPUTERNAME PSModulePath $newPSModulePathStr /m 2>&1) -Level 3
        $env:PSModulePath = $newPSModulePathStr
        Out-Log -Data "Added '$appxCmdletsPath' to PSModulePath." -Level 1

    } else {
        # No PSModulePath registry key/value exists, so don't worry about modifying it. Just create a new one.
        Out-Log -Data (& setx /s $env:COMPUTERNAME PSModulePath $appxCmdletsPath /m 2>&1) -Level 3
        $env:PSModulePath = $appxCmdletsPath
        Out-Log -Data "Added '$appxCmdletsPath' to PSModulePath." -Level 1
    }
}

Function Set-WinRMConfiguration {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SetWinRMConfigurationResponse )
    if ($SetWinRMConfigurationResponse) {
        Out-Log -Data (& winrm set winrm/config/client '@{AllowUnencrypted="true"}' 2>&1) -Level 3
        Out-Log -Data (& winrm set winrm/config/client '@{TrustedHosts="*"}' 2>&1) -Level 3
        Out-Log -Data 'WinRM successfully configured.' -Level 1
    }
}

Function Test-PowerShellVersion {
    # This function tests the version of PowerShell / Windows Management Framework and prompts the user
      # to download a newer version if necessary.
    if ($PSVersionTable.PSVersion.Major -ge 4) { return }

    Out-Log -Data 'Failed to install FastLinq REST API PowerKit: Windows Management Framework 4.0+ required.' -ForegroundColor Red -Level 0
    Out-Log -Data ' Please install WMF 4.0 or higher and run this script again.' -ForegroundColor Red -Level 0
    Out-Log -Data ' URL for WMF 4.0: https://www.microsoft.com/en-us/download/details.aspx?id=40855' -ForegroundColor Red -Level 0
    Out-Log -Data ' URL for WMF 5.0: https://www.microsoft.com/en-us/download/details.aspx?id=50395' -ForegroundColor Red -NoNewline -Level 0

    $title = 'Download Windows Management Framework 4.0+'
    $message = 'Windows Management Framework 4.0+ is required for FastLinq REST API PowerKit. Which WMF version would you like to download?'
    $4 = New-Object System.Management.Automation.Host.ChoiceDescription '&4.0', `
        'Windows Management Framework 4.0 (https://www.microsoft.com/en-us/download/details.aspx?id=40855)'
    $5 = New-Object System.Management.Automation.Host.ChoiceDescription '&5.0', `
        'Windows Management Framework 5.0 (https://www.microsoft.com/en-us/download/details.aspx?id=50395)'
    $none = New-Object System.Management.Automation.Host.ChoiceDescription '&None', `
        'None'
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($4, $5, $none)
    $tempResult = $host.ui.PromptForChoice($title, $message, $options, 1)
    if ($tempResult -eq 0) {
        Start-Process 'https://www.microsoft.com/en-us/download/details.aspx?id=40855'
    } elseif ($tempResult -eq 1) {
        Start-Process 'https://www.microsoft.com/en-us/download/details.aspx?id=50395'
    }
    # TODO - Add script exiting message.
    Exit
}

Function Test-RESTAPI {
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Url,
        [Parameter(Mandatory=$false)] [AllowNull()] $EncodedAuthString
    )

    try {
        if ($encodedAuthString -eq $null) {
            $response = (Invoke-RestMethod -Method Get -Uri $Url).value
        } else {
            $response = (Invoke-RestMethod -Method Get -Headers @{'Authorization' = "Basic $EncodedAuthString"} -Uri $Url).value
        }
        $response = ($response | Out-String).Trim()
        Out-Log -Data "`n$response`n" -Level 2
    }
    catch {
        $Global:errRest = $_
        Out-Log -Data "REST API test failed" -ForegroundColor Red -Level 0
        Out-Log -Data " StatusCode: $($_.Exception.Response.StatusCode.value__)" -ForegroundColor Red -Level 0
        Out-Log -Data " StatusDescription: $($_.Exception.Response.StatusDescription)" -ForegroundColor Red -Level 0
    }
}

Function Import-AllCmdlets {
    # This function imports all QLGC cmdlets into the current PowerShell session.
    # This function is necessary to update any cmdlet and cmdlet help changes that have occurred from one PowerKit
    # version to another. One consequence of force importing the QLGC cmdlets is the user will be prompted if they
    # want to run software from an unpublished publisher when this function is called rather than the first time
    # intellisense is invoked after the PowerKit is installed. Note that this prompt only occurs if it is the user's
    # first time installing the PowerKit. Of course, in this scenario this function call is not necessary, since the
    # cmdlets will not need to be refreshed.
    # TODO - Determine if this is the first time PowerKit has been installed / if user already trusts QLGC publisher.
    $moduleNames = (Get-Module -ListAvailable | Where-Object { $_.Name.StartsWith('QLGC_') }).Name
    foreach ($moduleName in $moduleNames) {
        try {
            Import-Module $moduleName -Force 2>&1 | Out-Null
        } catch {
            # ignore any exception when importing. ErrorAction and 2>&1 | Out-Null in above command does not work!
        }
    }
}

Function Install-PowerKit {
Param ( [Parameter(Mandatory=$false)] [AllowNull()] $Credential, $ConfigureWinRM, $InstallPoshSSHModule, $InstallRESTServer )
$NumberOfArgs = $PSBoundParameters.Count
# Encapulate installer logic here
#--------------------------------------------------------------------------------------------------
# Globals
$Script:CLSID = '{BC33E6C0-DB6A-4781-B924-CB1CDA88E4A1}'
$Script:isNano = Test-IfNano
$Script:isServerCore = Test-IfServerCore
$Script:savedAppxPolicyRegValData = $null
$cmdletsAppxFileName = Get-CmdletsAppxFileName
$Script:appcmd = "$env:SystemRoot\system32\inetsrv\appcmd.exe"
$Script:netsh = "$env:windir\system32\netsh.exe"
$Script:restServerZipFile = "$((Get-Location).path)\FastLinqProviderREST-WindowsServer.qlgc"
$Script:Verbose = 1

Add-Type -AssemblyName System.IO.Compression.FileSystem

Add-Type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@

[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

Test-ExecutionPolicy

if (-not $InstallRESTServerOnly) {
    # Check if PowerKit is already installed before proceeding
    if ($Script:isServerCore) {
        if (Test-Path -Path "$env:ProgramFiles\QLogic Corporation\PowerKit\Cmdlets") {
            Test-LastExitCode -ExitCode 1 -Message "Could not install PowerKit because it is already installed! Please uninstall the PowerKit first." -ExitScriptIfBadErrorCode
        }
    } else {
        if ((Get-AppxPackage -Name 'QLGCProvider') -ne $null) {
            Test-LastExitCode -ExitCode 1 -Message "Could not install PowerKit because it is already installed! Please uninstall the PowerKit first." -ExitScriptIfBadErrorCode
        }
    }

    #--------------------------------------------------------------------------------------------------
    # User Input
    # Write-Host "No of arguments $($NumberOfArgs) passed"

    if ($NumberOfArgs -eq 0) # Default Installation without args.
    {
        $response0 = Set-WinRMConfigurationPrompt
        $response1 = Install-PoshSSHModulePrompt
        $response2 = Install-RESTServerPrompt
    }
    else # using script params.
    {
        if ($ConfigureWinRM){
            $response0 = $true
        }

        if ($InstallPoshSSHModule){
            $response1 = $true
        }

        if ($InstallRESTServer){
            $response2 =$true
        }
    }


    #--------------------------------------------------------------------------------------------------
    # Script - Cmdlets
    $Script:CurrentInstallation = 'FastLinq PowerKit'
    if ($Script:isServerCore) {
        Install-ForServerCore -AppxFileName $cmdletsAppxFileName
        Add-CmdletsPathToPSModulePath
    } else {
        Add-AppxTrustedAppsRegistryKey
        Remove-CLSIDRegistryPaths
        Add-AppxPackageWrapper -AppxFileName $cmdletsAppxFileName
        Add-CLSIDRegistryPathsAndMofCompInstall
        Remove-AppxTrustedAppsRegistryKey
        Copy-CmdletsToProgramData
        Add-CmdletsPathToPSModulePath
        Copy-XMLTemplatesToProgramData
    }

    if ($ConfigureWinRM ) {
        Set-WinRMConfiguration -SetWinRMConfigurationResponse $response0
    }

    Out-Log -Data "Successfully installed FastLinq PowerKit.`n" -ForegroundColor Green -Level 0

    #--------------------------------------------------------------------------------------------------
    # Script - Install-PoshSSH Module
    # Install PoshSSH Module even if user selects yes for RestServer.
    if ($response1 -or $response2) {
        $Script:CurrentInstallation = 'Posh-SSH Module'

        try
        {
            $SetInstallationPolicy        = Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
            $PoshSSHInstalled             = Install-Module -Name Posh-SSH -Force
            [object []] $Is_Posh_Instaled = Find-Module -Name Posh-SSH
        }
        catch
        {
            write-host "An error occurred while installing Posh-SSH Module !!!" -ForegroundColor Yellow
        }
        finally
        {
            if (($Is_Posh_Instaled.Count -gt 0) -and ($Is_Posh_Instaled.Name -imatch "Posh-SSH")) 
            {
                if ($response1)
                {
                    write-host "Successfully installed Posh-SSH Module from PS Gallery." -ForegroundColor Green
                }
                else
                {
                    write-host "Note: Posh-Module is installed as per REST Server requirement." -ForegroundColor Yellow
                }   
            } 
            else
            {
               write-host "Failed to install Posh-SSH Module from PS Gallery !!!" -ForegroundColor Red
            }
        }
    }

    #--------------------------------------------------------------------------------------------------
    Import-AllCmdlets
    Out-Log -Data "Note: if this was an upgrade from a previous version, you may need to close all <WMIProviderHost> processes from Task-Manager (or) restart the system for the new provider to be loaded." -ForegroundColor Yellow -Level 0
}

#--------------------------------------------------------------------------------------------------
# Script - REST API
if ($response2 -or $InstallRESTServerOnly) {
    $Script:CurrentInstallation = 'FastLinq PowerKit REST Server'
    # Test-PowerShellVersion

    # Define REST server variables
    $restServerRegistryPath = 'HKLM:\Software\QLogic Corporation\PowerShell Cmdlets REST Server'
    $restServerInstallPath = "$env:ProgramFiles\QLogic Corporation\PowerShell Cmdlets REST Server"
    $restServerServiceName = 'QLogic PowerShell Cmdlets REST Server'

    # Check if REST server is already installed before proceeding
    if (Test-Path $restServerRegistryPath) {
        Test-LastExitCode -ExitCode 1 -Message "Could not install REST server because it is already installed! Please uninstall the REST server first." -ExitScriptIfBadErrorCode
    }

    # Confirm REST server installation file exists before proceeding
    if (-not (Test-Path -Path $Script:restServerZipFile)) {
        Test-LastExitCode -ExitCode 1 -Message "Could not install REST server because REST server installation file is missing." -ExitScriptIfBadErrorCode
    }

    Out-Log -Data "`nBeginning installation of $Script:CurrentInstallation" -ForegroundColor Cyan -Level 0

    # Check if running inside a container
    $runningInContainer = $env:USERNAME -eq 'ContainerAdministrator' -and $env:USERDOMAIN -eq 'User Manager'

    # Determine protocol
    if (-not [String]::IsNullOrEmpty($RESTServerProtocol)) {
        $restServerProtocol = $RESTServerProtocol.ToLower()
    } else {
        $restServerProtocol = Get-RESTServerProtocolPrompt
    }

    # Determine port
    $ports = [ordered]@{}
    if ($RESTServerPort -ne 0) {
        $ports[$restServerProtocol] = $RESTServerPort
    } else {
        if ($restServerProtocol -eq 'http' -or $restServerProtocol -eq 'both') {
            $ports['http'] = Get-RESTServerPortPrompt -Protocol 'http'
        }
        if ($restServerProtocol -eq 'https' -or $restServerProtocol -eq 'both') {
            $ports['https'] = Get-RESTServerPortPrompt -Protocol 'https'
        }
    }

    # Get credential object
    if ($Credential -eq $null) {
        $username = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
        $cred = Get-Credential -Message 'Enter Windows credentials to use for the REST server' -UserName $username
    } else {
        $cred = $Credential
    }
    $plainPassword = ConvertFrom-SecureToPlain -SecurePassword $cred.Password

    # Create install directory if doesn't exist and unzip files to it
    Out-Log -Data "`nCreating installation directory and extracting files" -ForegroundColor Cyan -Level 0
    if (-not (Test-Path $restServerInstallPath)) {
        Out-Log -Data (New-Item -ItemType Directory -Path $restServerInstallPath -Force) -Level 2
    }
    [System.IO.Compression.ZipFile]::ExtractToDirectory($restServerZipFile, $restServerInstallPath)

    # Add Windows registry entries
    Out-Log -Data "`nAdding Windows registry entries" -ForegroundColor Cyan -Level 0
    Add-ManyToRegistryWriteOver -Path $restServerRegistryPath -NameTypeValueArray @(
        @('container', 'DWord', $runningInContainer),
        @('path', 'String', $restServerInstallPath),
        @('protocol', 'String', $restServerProtocol),
        @('http_port', 'DWord', $ports.http),
        @('https_port', 'DWord', $ports.https) )


    if ($restServerProtocol -eq 'http' -or $restServerProtocol -eq 'both') {
        # Register URL with Windows
        Out-Log -Data "`nRegistering HTTP URL with Windows" -ForegroundColor Cyan -Level 0
        Out-Log -Data (netsh.exe http add urlacl url=http://localhost:$($ports.http)/ user=\Everyone) -Level 2
        Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to register HTTP URL with Windows; REST API may not operate correctly."
    }

    if ($restServerProtocol -eq 'https' -or $restServerProtocol -eq 'both') {
        
        # Prompt user for existing HTTPS certificate (will be null if user selects to use self-signed certificate)
        if ($CreateSelfSignedHTTPSCert -eq $true) {
            $existingCertPath = $null
        } else {
            $existingCertPath = Get-RESTServerExistingHTTPSCert
        }

        # Create self-signed certificate
        if ($existingCertPath -eq $null) {
            Out-Log -Data "`nCreating and importing HTTPS self-signed certificate" -ForegroundColor Cyan -Level 0
            
            # Generate random filename for certificate file
            $certPath = "$($(New-Guid).Guid).pfx"

            # Prompt user for HTTPS certificate password
            if ($HTTPSCertPassword -ne $null) {
                $password = $HTTPSCertPassword
            } else {
                $password = Get-RESTServerHTTPSCertPasswordPrompt
            }

            # Create HTTPS certificate
            $thumb = (New-SelfSignedCertificate -CertStoreLocation cert:\localmachine\my -DnsName 'localhost').Thumbprint
            Out-Log -Data (Export-PfxCertificate -cert "cert:\localmachine\my\$thumb" -FilePath $certPath -Password $password) -Level 2
            Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to create HTTPS certificate" -ExitScriptIfBadErrorCode

            # Import HTTPS certificate
            $passwordText = ConvertFrom-SecureToPlain -SecurePassword $password
            Out-Log -Data (certutil.exe -p $passwordText -importpfx $certPath) -Level 2
            Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to import the HTTPS certificate" -ExitScriptIfBadErrorCode
        }
        
        # Use existing certificate
        else {
            Out-Log -Data "`nImporting HTTPS certificate" -ForegroundColor Cyan -Level 0
            while ($true) {
                # Prompt user for HTTPS certificate password
                if ($HTTPSCertPassword -ne $null) {
                    $password = $HTTPSCertPassword
                } else {
                    $password = Get-RESTServerHTTPSCertPasswordPromptSimple
                }

                # Verify password is correct
                $passwordIsCorrect = Test-HTTPSCertPassword -CertificateFilePath $existingCertPath -SecurePassword $password
                if ($passwordIsCorrect) {
                    break;
                }
                Out-Log -Data "`nPassword was incorrect; please try again." -Level 0
            }

            # Import HTTPS certificate
            $passwordText = ConvertFrom-SecureToPlain -SecurePassword $password
            Out-Log -Data (certutil.exe -p $passwordText -importpfx $existingCertPath) -Level 2
            Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to import the HTTPS certificate"

            # Get thumbprint
            $dumpOutput = certutil.exe -p $passwordText -dump $existingCertPath 2>&1
            $line = $dumpOutput | Where-Object {$_ -match 'Cert Hash'}
            $thumb = $line.Split(':')[1].replace(' ', '')
        }


        # Register URL with Windows
        Out-Log -Data "`nRegistering HTTPS URL with Windows" -ForegroundColor Cyan -Level 0
        Out-Log -Data (netsh.exe http add urlacl url=https://localhost:$($ports.https)/ user=\Everyone) -Level 2
        Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to register HTTPS URL with Windows; REST API may not operate correctly."

        $uuid = "{$($(New-Guid).Guid)}"
        Out-Log -Data (netsh.exe http add sslcert ipport=0.0.0.0:$($ports.https) certhash=$thumb appid=$uuid) -Level 2

        # Store HTTPS thumbprint in Windows registry
        Out-Log -Data "`nAdding Windows registry entry" -ForegroundColor Cyan -Level 0
        Add-ManyToRegistryWriteOver -Path $restServerRegistryPath -NameTypeValueArray @(
            @('thumbprint', 'String', $thumb) )

        # Delete cert.pfx file
        if ($existingCertPath -eq $null -and $certPath -ne $null) {
            Remove-Item -Path $certPath -Force
        }
    }

    # Add new firewall rule(s)
    if (-not $runningInContainer) {
        foreach ($protocol in $ports.Keys) {
            $port = $ports.Item($protocol)
            if ($port -ne $null) {
                Out-Log -Data (New-NetFirewallRule -DisplayName "$restServerServiceName - $($protocol.ToUpper())" -Direction Inbound -Action Allow -Protocol TCP -LocalPort $port) -Level 3
            }
        }
    }


    # Install as a Windows service
    Out-Log -Data "`nInstalling Windows service" -ForegroundColor Cyan -Level 0
    Out-Log -Data (& "$restServerInstallPath\PowerShellCmdletsRestServer.exe" -install) -Level 2
    Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to install Windows service" -ExitScriptIfBadErrorCode
    

    # Wait for service to start before continuing
    Out-Log -Data "`nWaiting for service to start before continuing." -ForegroundColor Cyan -Level 0
    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    $timeoutSecs = 60
    while ($true) {
        Start-Sleep -Milliseconds 2500
        $service = Get-Service -Name $restServerServiceName
        if ($service -ne $null -and $service.Status -eq 'Running') {
            Out-Log -Data '... service started!' -ForegroundColor Cyan -Level 0
            break;
        }
        if ($stopwatch.Elapsed.TotalSeconds -gt $timeoutSecs) {
            Test-LastExitCode -ExitCode 1 -Message "`n$restServerServiceName service never started after $timeoutSecs seconds; giving up." -ExitScriptIfBadErrorCode
        }
        Out-Log -Data 'Still waiting for service to start ...' -ForegroundColor Cyan -Level 0
    }

    # # Change running user of Windows service
    # Out-Log -Data "`nChanging running user of Windows service" -ForegroundColor Cyan -Level 0
    # $filter = "Name='QLogic PowerShell Cmdlets REST Server'"
    # $service = Get-WMIObject -namespace "root\cimv2" -class Win32_Service -Filter $filter
    # Out-Log -Data ($service.Change($null, $null, $null, $null, $null, $null, $cred.UserName, $plainPassword)) -Level 2
    # Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to change running user of Windows service; REST API may not operate correctly."
    
    # Out-Log -Data ($service.StopService()) -Level 2
    # Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to stop Windows service" -ExitScriptIfBadErrorCode
    
    # while ($service.Started) {
    # Out-Log -Data 'Waiting for service to stop ...' -Level 2
    # Start-Sleep -Milliseconds 1000
    # $service = Get-WMIObject -namespace "root\cimv2" -class Win32_Service -Filter $filter
    # }
    # Out-Log -Data $service.StartService() -Level 2
    # Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to start Windows service" -ExitScriptIfBadErrorCode


    # Get base64 credential to use for invoking REST
    $encodedAuthString = Convert-CredentialToHeaderAuthorizationString -Credential $cred
    Out-Log -Data "`nUse this string in the REST API request header:" -Level 0
    Out-Log -Data " Authorization: Basic $encodedAuthString" -Level 0 -ForegroundColor Gray

    foreach ($protocol in $ports.Keys) {
        $port = $ports.Item($protocol)
        if ($port -ne $null) {
            
            # Test REST endpoint #1
            $url = "$protocol`://localhost:$port/ping"
            Out-Log -Data "`nTesting $($protocol.ToUpper()) REST API with $url (no authentication required)" -ForegroundColor Cyan -Level 0
            Test-RESTAPI -Url $url

            # Test REST endpoint #2
            $url = "$protocol`://localhost:$port/pingAuth"
            Out-Log -Data "Testing $($protocol.ToUpper()) REST API with $url (requires authentication)" -ForegroundColor Cyan -Level 0
            Test-RESTAPI -Url $url -EncodedAuthString $encodedAuthString
        }
    }

    Out-Log -Data "`nSuccessfully installed FastLinq PowerKit REST Server." -ForegroundColor Green -Level 0
}
}

Export-ModuleMember -function Install-PowerKit