InstallModule.psm1


# -------------------------- Load Script Files ----------------------------
#
# Do not add Export-ModuleMember logic. All functions in .\Public are added to the manifest during build.
# If you need to import from \src\ run the build.ps1 in \tools\ with 'ExportFunctionsToSrc' as the task.
#
$ModuleScriptFiles = @(Get-ChildItem -Path $PSScriptRoot -Filter *.ps1 -Recurse  | Where-Object { $_.Name -notlike "*.ps1xml" } )

foreach ($ScriptFile in $ModuleScriptFiles) {
    try {
       Write-Verbose "Loading script file $($ScriptFile.Name)"
        . $ScriptFile.FullName
    }
    catch {
       Write-Error "Error loading script file $($ScriptFile.FullName)"
    }
}
#---------------------------------------------------------------------------------
#The sample scripts are not supported under any Microsoft standard support
#program or service. The sample scripts are provided AS IS without warranty
#of any kind. Microsoft further disclaims all implied warranties including,
#without limitation, any implied warranties of merchantability or of fitness for
#a particular purpose. The entire risk arising out of the use or performance of
#the sample scripts and documentation remains with you. In no event shall
#Microsoft, its authors, or anyone else involved in the creation, production, or
#delivery of the scripts be liable for any damages whatsoever (including,
#without limitation, damages for loss of business profits, business interruption,
#loss of business information, or other pecuniary loss) arising out of the use
#of or inability to use the sample scripts or documentation, even if Microsoft
#has been advised of the possibility of such damages
#---------------------------------------------------------------------------------

#requires -version 2.0

<#
     .SYNOPSIS
        The PowerShell script which can be used to add trusted sites in Internet Explorer.
    .DESCRIPTION
        The PowerShell script which can be used to add trusted sites in Internet Explorer.
    .PARAMETER TrustedSites
        Spcifies the trusted site in Internet Explorer.
    .PARAMETER HTTP
        Once you use the HTTP switch parameter, the domain will be use the http:// prefix.
    .PARAMETER PrimaryDomain
        Spcifies the primary domain in Internet Explorer.
    .PARAMETER SubDomain
        Spcifies the sub domain in Internet Explorer.
    .EXAMPLE
        C:\PS> C:\AddingTrustedSites.ps1 -TrustedSites "contoso1.com","contoso2.com" -HTTP
 
        Successfully added 'contoso1.com' and 'contoso2.com' domain to trusted sites in Internet Explorer.
 
        This command will add 'contoso1.com' and 'contoso2.com' domain to trusted sites in Internet Explorer respectively.
    .EXAMPLE
        C:\PS> C:\AddingTrustedSites.ps1 -PrimaryDomain "contoso.com" -SubDomain "test.domain"
 
        Successfully added 'test.domain.contoso.com' domain to trusted sites in Internet Explorer.
 
        This command will add 'test.domain.contoso.com' domain to trusted sites in Internet Explorer.
#>

function Add-TrustedSite {
    Param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "SingleDomain")]
        [Alias('Sites')][ValidateNotNullOrEmpty()]
        [String[]]$TrustedSites,

        [Parameter(Mandatory = $false)]
        [Switch]$HTTP,

        [Parameter(Mandatory = $true, ParameterSetName = "CombineDomain")]
        [Alias('pdomain')][ValidateNotNullOrEmpty()]
        [String]$PrimaryDomain,

        [Parameter(Mandatory = $true, ParameterSetName = "CombineDomain")]
        [Alias('sdomain')][ValidateNotNullOrEmpty()]
        [String]$SubDomain
    )

    #Initialize key variables
    $UserRegPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains"

    $DWord = 2

    #Main function
    Function CreateKeyReg {
        Param
        (
            [String]$KeyPath,
            [String]$Name
        )

        If (Test-Path -Path $KeyPath) {
            Write-Verbose "Creating a new key '$Name' under $KeyPath."
            New-Item -Path "$KeyPath" -ItemType File -Name "$Name" `
                -ErrorAction SilentlyContinue | Out-Null
        }
        Else {
            Write-Warning "The path '$KeyPath' not found."
        }
    }

    Function SetRegValue {
        Param
        (
            [Boolean]$blnHTTP = $false,
            [String]$RegPath
        )

        Try {
            If ($blnHTTP) {
                Write-Verbose "Creating a Dword value named 'HTTP' and set the value to 2."
                Set-ItemProperty -Path $RegPath -Name "http" -Value $DWord `
                    -ErrorAction SilentlyContinue | Out-Null
            }
            Else {
                Write-Verbose "Creating a Dword value named 'HTTPS' and set the value to 2."
                Set-ItemProperty -Path $RegPath -Name "https" -Value $DWord `
                    -ErrorAction SilentlyContinue | Out-Null
            }
        }
        Catch {
            Write-Verbose "Failed to add trusted sites in Internet Explorer." -BackgroundColor Red
        }
    }

    If ($TrustedSites) {
        #Adding trusted sites in the registry
        Foreach ($TruestedSite in $TrustedSites) {
            #If user does not specify the user type. By default,the script will add the trusted sites for the current user.

            If ($HTTP) {
                CreateKeyReg -KeyPath $UserRegPath -Name $TruestedSite
                SetRegValue -RegPath "$UserRegPath\$TruestedSite" -blnHTTP $true -DWord $DWord
                Write-Verbose "Successfully added '$TruestedSite' domain to trusted Sites in Internet Explorer."
            }
            Else {
                CreateKeyReg -KeyPath $UserRegPath -Name $TruestedSite
                SetRegValue -RegPath "$UserRegPath\$TruestedSite" -blnHTTP $false -DWord $DWord
                Write-Verbose "Successfully added '$TruestedSite' domain to to trusted Sites in Internet Explorer."
            }
        }
    }
    Else {
        #Setting the primary domain and sub-domain
        If ($HTTP) {
            CreateKeyReg -KeyPath $UserRegPath -Name $PrimaryDomain
            CreateKeyReg -KeyPath "$UserRegPath\$PrimaryDomain" -Name $SubDomain
            SetRegValue -RegPath "$UserRegPath\$PrimaryDomain\$SubDomain" -blnHTTP $true -DWord $DWord
            Write-Verbose "Successfully added $SubDomain.$PrimaryDomain' domain to trusted Sites in Internet Explorer."
        }
        Else {
            CreateKeyReg -KeyPath $UserRegPath -Name $PrimaryDomain
            CreateKeyReg -KeyPath "$UserRegPath\$PrimaryDomain" -Name $SubDomain
            SetRegValue -RegPath "$UserRegPath\$PrimaryDomain\$SubDomain" -blnHTTP $false -DWord $DWord
            Write-Verbose "Successfully added '$SubDomain.$PrimaryDomain' domain to trusted Sites in Internet Explorer."
        }
    }
}
function Compare-Hashtable {
    <#
    .SYNOPSIS
    Compare two Hashtable and returns an array of differences.
    .DESCRIPTION
    The Compare-Hashtable function computes differences between two Hashtables. Results are returned as
    an array of objects with the properties: "key" (the name of the key that caused a difference),
    "side" (one of "<=", "!=" or "=>"), "lvalue" an "rvalue" (resp. the left and right value
    associated with the key).
    .PARAMETER left
    The left hand side Hashtable to compare.
    .PARAMETER right
    The right hand side Hashtable to compare.
    .EXAMPLE
    Returns a difference for ("3 <="), c (3 "!=" 4) and e ("=>" 5).
    Compare-Hashtable @{ a = 1; b = 2; c = 3 } @{ b = 2; c = 4; e = 5}
    .EXAMPLE
    Returns a difference for a ("3 <="), c (3 "!=" 4), e ("=>" 5) and g (6 "<=").
    $left = @{ a = 1; b = 2; c = 3; f = $Null; g = 6 }
    $right = @{ b = 2; c = 4; e = 5; f = $Null; g = $Null }
    Compare-Hashtable $left $right
    #>

    [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [Hashtable]$Left,

            [Parameter(Mandatory = $true)]
            [Hashtable]$Right
        )

        function NewResult($Key, $LValue, $Side, $RValue) {
            New-Object -Type PSObject -Property @{
                        key    = $Key
                        lvalue = $LValue
                        rvalue = $RValue
                        side   = $Side
                }
        }
        [Object[]]$Results = $Left.Keys | ForEach-Object {
            if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
                NewResult -Key $_ -LValue $Left[$_] -Side "<=" -RValue $Null
            } else {
                $LValue, $RValue = $Left[$_], $Right[$_]
                if ($LValue -ne $RValue) {
                    NewResult -Key $_ -LValue $LValue -Side "!=" -RValue $RValue
                }
            }
        }
        $Results += $Right.Keys | ForEach-Object {
            if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
                NewResult -Key $_ -LValue $Null -Side "=>" -RValue $Right[$_]
            }
        }
        $Results
    }
function ConvertTo-PlainText {
    param (
        [SecureString]$EncryptedString
    )
    $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($EncryptedString)
    Return [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
}
function Get-CredentialManagerCredential {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
    param (
        [String]$Target,
        [String]$User='User'
    )
    ConvertFrom-CredMan(credman -GetCred -Target "$(if ($User){"$User@$Target"} else {$Target})")
}
<#
.SYNOPSIS
Function to return the download folder
 
.DESCRIPTION
Function to return the download folder since it is not a standard well known folder
 
.EXAMPLE
Get-DownloadFolder
 
.NOTES
Author: Mark Evans
General notes
#>

function Get-DownloadFolder {
    Get-KnownFolderPath -KnownFolder 'Downloads'
}
function Get-EnvironmentVariable {
    param (
        [string]$Name
    )
    foreach ($item in [enum]::getvalues([System.EnvironmentVariableTarget])) {
        $result = [System.Environment]::GetEnvironmentVariable($Name,$item)
        if ($result){
            break
        }
    }
    $result
}
function Get-InstalledSoftware {
    <#
    .SYNOPSIS
        Retrieves a list of all software installed on a Windows computer.
    .EXAMPLE
        PS> Get-InstalledSoftware
 
        This example retrieves all software installed on the local computer.
    .PARAMETER ComputerName
        If querying a remote computer, use the computer name here.
 
    .PARAMETER Name
        The software title you'd like to limit the query to.
 
    .PARAMETER Guid
        The software GUID you'e like to limit the query to
    #>

    [CmdletBinding()]
    param (

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter()]
        [guid]$Guid
    )
    process {

        #$args[0].GetEnumerator() | ForEach-Object { New-Variable -Name $_.Key -Value $_.Value }

        $UninstallKeys = @(
            "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall",
            "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
        )
        New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS | Out-Null
        $UninstallKeys += Get-ChildItem HKU: | Where-Object { $_.Name -match 'S-\d-\d+-(\d+-){1,14}\d+$' } | ForEach-Object {
            "HKU:\$($_.PSChildName)\Software\Microsoft\Windows\CurrentVersion\Uninstall"
        }
        if (-not $UninstallKeys) {
            Write-Warning -Message 'No software registry keys found'
        }
        else {
            foreach ($UninstallKey in $UninstallKeys) {
                $friendlyNames = @{
                    'DisplayName'    = 'Name'
                    'DisplayVersion' = 'Version'
                }
                Write-Verbose -Message "Checking uninstall key [$($UninstallKey)]"
                if ($Name) {
                    $WhereBlock = { $_.GetValue('DisplayName') -like "$Name*" }
                }
                elseif ($GUID) {
                    $WhereBlock = { $_.PsChildName -eq $Guid.Guid }
                }
                else {
                    $WhereBlock = { $_.GetValue('DisplayName') }
                }
                $SwKeys = Get-ChildItem -Path $UninstallKey -ErrorAction SilentlyContinue | Where-Object $WhereBlock
                if (-not $SwKeys) {
                    Write-Verbose -Message "No software keys in uninstall key $UninstallKey"
                }
                else {
                    foreach ($SwKey in $SwKeys) {
                        $output = @{ }
                        foreach ($ValName in $SwKey.GetValueNames()) {
                            if ($ValName){
                            if ($ValName -ne 'Version') {
                                $output.InstallLocation = ''
                                if ($ValName -eq 'InstallLocation' -and
                                    ($SwKey.GetValue($ValName)) -and
                                    (@('C:', 'C:\Windows', 'C:\Windows\System32', 'C:\Windows\SysWOW64') -notcontains $SwKey.GetValue($ValName).TrimEnd('\'))) {
                                    $output.InstallLocation = $SwKey.GetValue($ValName).TrimEnd('\')
                                }
                                [string]$ValData = $SwKey.GetValue($ValName)
                                if ($friendlyNames[$ValName]) {
                                    $output[$friendlyNames[$ValName]] = $ValData.Trim() ## Some registry values have trailing spaces.
                                }
                                else {
                                    $output[$ValName] = $ValData.Trim() ## Some registry values trailing spaces
                                }
                            }}
                        }
                        $output.GUID = ''
                        if ($SwKey.PSChildName -match '\b[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}\b') {
                            $output.GUID = $SwKey.PSChildName
                        }
                        if ($output.name -eq 'Microsoft .NET Framework 4 Multi-Targeting Pack'){
                            Write-Output 'Test'
                        }
                        New-Object -TypeName PSObject -Prop $output
                    }
                }
            }
        }
    }
}
Function Get-ListeningTCPConnections {
    [cmdletbinding()]
    param(
    )

    try {
        $TCPProperties = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
        $Connections = $TCPProperties.GetActiveTcpListeners()
        foreach ($Connection in $Connections) {
            if ($Connection.address.AddressFamily -eq "InterNetwork" ) { $IPType = "IPv4" } else { $IPType = "IPv6" }

            $OutputObj = New-Object -TypeName PSobject
            $OutputObj | Add-Member -MemberType NoteProperty -Name "LocalAddress" -Value $connection.Address
            $OutputObj | Add-Member -MemberType NoteProperty -Name "ListeningPort" -Value $Connection.Port
            $OutputObj | Add-Member -MemberType NoteProperty -Name "IPV4Or6" -Value $IPType
            $OutputObj
        }

    }
    catch {
        Write-Error "Failed to get listening connections. $_"
    }
}
function Get-NewPassword {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
    param (
        [Int]$Length = 15
    )
    try {
        [reflection.assembly]::loadwithpartialname("system.web") | Out-Null
    }
    catch {
        Write-Log -Level ERROR -Message 'Unable to load module to create new password'
    }
    do {
        $Password = [System.Web.Security.Membership]::GeneratePassword($Length, 3)
    } until (!($Password.Contains(';') -or ($Password.Contains("'"))))

    Return ConvertTo-SecureString -String "$Password" -AsPlainText -Force
}
function Get-WebHostingURI ($Channel) {
    $VersionURL = "https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/$Channel/latest.version"
    $CurrentProgressPreference = $ProgressPreference
    $ProgressPreference = 'SilentlyContinue' # Subsequent calls do not display UI.
    $RequestResult = Invoke-WebRequest -UseBasicParsing -Uri $VersionURL
    if ($RequestResult.StatusCode -eq 200) {
        $LatestVersion = $RequestResult.Content
    }
    else {
        Write-Error -Message "Error Checking Version $($RequestResult.StatusDescription)"
        Exit
    }
    $URL = "https://dotnet.microsoft.com/download/dotnet/thank-you/runtime-aspnetcore-$LatestVersion-windows-hosting-bundle-installer"
    #ensure we get a response even if an error's returned
    $response = try {
        $RequestResult = Invoke-WebRequest -Uri $URL -UseBasicParsing -ErrorAction Stop
        $RequestResult.BaseResponse
    }
    catch [System.Net.Http.HttpRequestException] {
        Write-Verbose "An exception was caught: $($_.Exception.Message)"
        $_.Exception.Response
    }
    $ProgressPreference = $CurrentProgressPreference # Subsequent calls do display UI.
    if ($response.StatusCode -eq 200) {
        $FileUri = [Uri]($RequestResult.content | select-string "window.open\("".*?""" | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value).Replace("window.open(", '').replace('"', '')
    }
    else {
        Write-Error -Message "Error Checking Version $($response)"
        Return $null
    }
    Return $FileUri
}


Function New-ScriptParameterFolder {
    <#
    .SYNOPSIS
        Script to Allow user to input values for script parameters
 
    .NOTES
        Name: New-ScriptParameterFolder
        Author: Mark Evans <mark@madspaniels.co.uk>
        Version: 1.0
        DateCreated: 2021-06-23
 
    .EXAMPLE
        New-ScriptParameterFolder
 
 
    .LINK
 
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [String]$CommandPath = $MyInvocation.PSCommandPath
    )
    $script = Get-ChildItem -Path $CommandPath
    $scriptParameters = (Get-Command $Script).Parameters.GetEnumerator() | Where-Object { $_.Value.ParameterType -eq [System.IO.DirectoryInfo] }
    foreach ($item in $scriptParameters) {
        $Param = $item.Value
        [System.IO.DirectoryInfo]$Folder = $PSCmdlet.SessionState.PSVariable.GetValue($Param.Name)
        If ($Folder -and !($Folder.Exists) -and $PSCmdlet.ShouldProcess($Directory.Name, "Create Folder")) {
            Write-Log -Level WARNING -Message "Creating Folder {0}" -Arguments $Directory.FullName
            $Folder = New-Item $Folder.FullName -ItemType Directory -Force
            $PSCmdlet.SessionState.PSVariable.Set($Param.Name, $Folder)
        }
    }
}
function New-Shortcut {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [System.IO.FileInfo]
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = "Item")]
        [IO.FileInfo] $Target,
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = "Name")]
        [string] $TargetPath,
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = "Link")]
        [System.Uri]$TargetLink,
        [String]
        $ShortcutName,
        [String]
        $ShortcutFolder,
        [switch]
        $Desktop,
        [switch]
        $AllUsers
    )
    if ($PSCmdlet.ParameterSetName -eq "Link") {
        $TargetPath = $TargetLink.AbsoluteUri
        if (!($ShortcutName)) {
            $ShortcutName = $TargetLink.Segments | Select-Object -Last 1
        }
    }
    else {
        if ($PSCmdlet.ParameterSetName -eq "Item") {
            $TargetPath = Get-ChildItem $Target.FullName
        }
        if (!(Test-Path -Path $TargetPath -PathType Leaf)) {
            throw [System.IO.FileNotFoundException] "$TargetPath not found."
        }
        if ($PSCmdlet.ParameterSetName -eq 'Name') {
            $Target = Get-ChildItem $TargetPath
        }
        if (!($ShortcutName)) {
            $ShortcutName = $Target.BaseName
        }
    }
    if (!($ShortcutName.EndsWith('.lnk'))) { $ShortcutName += '.lnk' }

    if ($AllUsers) {
        if ($Desktop) {
            $ShortcutRoot = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::CommonDesktopDirectory)
        }
        else {
            $ShortcutRoot = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::CommonStartMenu)
        }
    }
    else {
        if ($Desktop) {
            $ShortcutRoot = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::DesktopDirectory)
        }
        else {
            $ShortcutRoot = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::StartMenu)
        }
    }
    If (!($Desktop)) {
        $ShortcutRoot = Join-Path $ShortcutRoot 'Programs'
    }
    if ($ShortcutFolder) {
        $ShortcutRoot = Join-Path $ShortcutRoot $ShortcutFolder
        New-Item -Path $ShortcutRoot -ItemType Directory -Force | Out-Null
    }
    $ShortcutLink = Join-Path $ShortcutRoot $ShortcutName

    if ($PSCmdlet.ShouldProcess("$ShortcutLink", "Create")) {
        $WshShell = New-Object -comObject WScript.Shell
        $Shortcut = $WshShell.CreateShortcut($ShortcutLink)
        $Shortcut.TargetPath = $TargetPath
        $Shortcut.Save()
        return (Get-ChildItem $ShortcutLink)
    }
    return
}
Function Read-ScriptParameter {
    <#
    .SYNOPSIS
        Script to Allow user to input values for script parameters
 
    .NOTES
        Name: Read-ScriptParameter
        Author: Mark Evans <mark@madspaniels.co.uk>
        Version: 1.1
        DateCreated: 2021-02-05
 
    .EXAMPLE
        Read-ScriptParameter
 
 
    .LINK
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Scope = 'Function', Target = '*')]
    [CmdletBinding()]
    param(
        [switch]$Reset,
        [String]$CommandPath = $MyInvocation.PSCommandPath
    )
    $script = Get-ChildItem -Path $CommandPath
    $scriptParameters = (Get-Command $Script).Parameters.GetEnumerator() | Where-Object { $_.Key -notin ([System.Management.Automation.Cmdlet]::CommonParameters) }
    $ast = (Get-Command $Script).ScriptBlock.Ast
    foreach ($item in $scriptParameters) {
        $Param = $item.Value
        #$Param.Attributes.Add([ValueAttribute]::new('Test','One','two'))
        $CurrentValue = $PSCmdlet.SessionState.PSVariable.GetValue($Param.Name)
        $ParamDefinition = ($ast.ParamBlock.Parameters | Where { $_.Name.VariablePath -like $Param.Name })
        if ($ParamDefinition.DefaultValue) {
            $CurrentDefaultValue = (Invoke-Expression $ParamDefinition.DefaultValue) -As $ParamDefinition.StaticType
        }
        if ($CurrentValue -and $CurrentValue.ToString() -eq $CurrentDefaultValue.ToString()){
            $PSCmdlet.SessionState.PSVariable.Set($Param.Name,$null)
        }
    }
    foreach ($item in $scriptParameters) {
        $Param = $item.Value
        Write-Log -Level Verbose -Message "Updating Parameter {0}" -Arguments $Param.Name
        $value = $null
        # Get Help Message
        $Message = ($Param.Attributes | Where-Object { $_.TypeId -eq [System.Management.Automation.ParameterAttribute] }).HelpMessage
        # If validateion Set get allowed values
        $Set = ($Param.Attributes | Where-Object { $_.TypeId -eq [System.Management.Automation.ValidateSetAttribute] } ).ValidValues
        If ($Set) {
            $SetString = "($($Set -join ','))"
        }
        else {
            $SetString = ""
        }
        # Load default value from Parameter File if exists or use default parameter
        $DefaultValues = @{}
        $ParamDataFile = Join-Path (Split-Path $PROFILE.CurrentUserAllHosts -Parent) "$($Param.Name).xml"
        If ($Reset -and (Test-Path $ParamDataFile)) {
            Remove-Item $ParamDataFile -Force
            Write-Log -Level DEBUG -Message "Parameter {0} Saved Value reset" -Arguments $Param.Name
        }
        If ($Reset) {
            if ($Param.Name -like 'Env*') {
                Set-EnvironmentVariable -Name $param.Name.Remove(0, 3).Replace('PATH', '') -Value $null -Scope Machine
                Set-EnvironmentVariable -Name $param.Name.Remove(0, 3).Replace('PATH', '') -Value $null -Scope User
                [System.Environment]::SetEnvironmentVariable($param.Name.Remove(0, 3).Replace('PATH', ''), $null)
                Write-Log -Level DEBUG -Message "Parameter {0} Environment Value reset" -Arguments $Param.Name
            }
        }
        # If Param DataFile exists retrieve saved value
        If (Test-Path $ParamDataFile -PathType Leaf) {
            $DefaultValues["Saved"] = Import-Clixml $ParamDataFile
        }
        else {
            $DefaultValues["Saved"] = $null
        }
        # Retrieve any value from the calling script
        $DefaultValues["Script"] = $PSCmdlet.SessionState.PSVariable.GetValue($Param.Name)
        If ($Param.Name.ToLower().StartsWith('env')) {
            $DefaultValues["Environment"] = [System.Environment]::GetEnvironmentVariable($param.Name.Remove(0, 3).Replace('PATH',''))
        }
        else {
            $DefaultValues["Environment"] = $null
        }
        # If default value defined in script retrieve expression and determine value
        $ParamDefinition = ($ast.ParamBlock.Parameters | Where { $_.Name.VariablePath -like $Param.Name })
        if ($ParamDefinition.DefaultValue) {
            $DefaultValues["Definition"] = Invoke-Expression $ParamDefinition.DefaultValue
        }
        else {
            if ($Param.SwitchParameter) {
                $DefaultValues["Definition"] = $false
            }
            else {
                $DefaultValues["Definition"] = $null
            }
        }
        # For Secure String retrieve value from credential manager
        If ($Param.ParameterType -eq [securestring]) {
            #Retrieve Password from credential manager assuming user parameter also exists
            $UserParamName = $Param.Name.Replace('Password', 'User')
            if ($scriptParameters.Key -contains $UserParamName) {
                $UserName = $PSCmdlet.SessionState.PSVariable.GetValue($UserParamName)
                If (!($UserName)) {
                    $UserName = $Param.Name
                }
                $Target="PowerShell"
                $UserCredential = Get-CredentialManagerCredential -Target $Target -User $UserName
                $DefaultValues["Credential"] = $UserCredential.SecurePass
                If ($Message -and $UserCredential.SecurePass) {
                    $Message = "$Message - Default from Credential Manager"
                }
            }
        }
        $DefaultValues.Keys | ForEach-Object {
            Write-Log -Level Verbose -Message "[{0}]`t{1}" -Arguments $_, $DefaultValues[$_]
        }

        # Select Default Value
        if ($DefaultValues["Credential"]) {
            $defaultset = "Credential"
        }
        elseif (($DefaultValues["Script"]) -and $DefaultValues["Script"] -ne $DefaultValues["Definition"]) {
            $defaultset = "Script"
        }
        elseif ($Param.Name.ToLower().StartsWith('env') -and $DefaultValues["Environment"]) {
            $defaultset = "Environment"
        }
        elseif ($DefaultValues["Saved"]) {
            $defaultset = "Saved"
        }
        else {
            $defaultset = "Definition"
        }
        $default = $DefaultValues[$defaultset]
        Write-Log -Level VERBOSE -Message "[{0}] default {1} from {2}" -Arguments $param.Name, $default, $defaultset

        If (!([string]::IsNullOrEmpty($Message))) {
            If (Test-Interactive) {
                # If running interactively ask User to enter value - repeat until valid value if validate set
                do {
                    Wait-Logging
                    if ($Param.ParameterType -eq [securestring] -and $default) {
                        $defaultString = '**********'
                    }
                    else {
                        $defaultString = $default
                    }
                    if (!($value = Read-Host -Prompt "$Message $SetString [$defaultString]"  -AsSecureString:($Param.ParameterType -eq [securestring]))) { $value = $default -as $Param.ParameterType }
                    if ($value.GetType() -eq [securestring] -and $value.Length -eq 0){
                        $value = $default
                    }
                } while ($value -notin $set -and $set)
            }
            else {
                # If not interactive set default value
                $value = $default
            }
            $UpdateDefault = ($value -ne $default)
            # For switch parameters convert value to boolean
            if ($Param.SwitchParameter -and $value) {
                if ($value.ToString() -eq "False" -or $value.ToString() -eq '0') {
                    $value = $false
                }
                else {
                    $value = $true
                }
            }
            # Set calling script variable to value
            $PSCmdlet.SessionState.PSVariable.Set($Param.Name, $Value)
            Set-Variable $Param.Name -Value $value
        }
        # If Script Variable changed from default save to XML
        if ($UpdateDefault) {
            Write-Log -Level WARNING -Message "Updating Default for {0} to {1}" -Arguments $Param.Name, "$(if ($Param.ParameterType -eq [securestring]){'******'}else{$value})"
            if ($Param.ParameterType -eq [securestring] -and ($UserParamName) -and ($value)) {
                $null = Set-CredentialManagerCredential -Target $Target -Pass $value -User $UserName -Comment "Set by install script $(Get-Date)"
            }
            elseif ($Param.Name -like 'ENV*') {
                Set-EnvironmentVariable -Name $param.Name.Remove(0, 3).Replace('PATH', '') -Value $value -Scope Machine
            }
            elseif ($PSCmdlet.SessionState.PSVariable.GetValue($Param.Name) -ne $DefaultValues["Definition"]) {
                if ((Test-Path $ParamDataFile) -and (get-item $ParamDataFile -Force).Attributes.HasFlag([System.IO.FileAttributes]::Hidden)) {
                    (get-item $ParamDataFile -force).Attributes -= 'Hidden'
                }
                Export-Clixml $ParamDataFile -InputObject $PSCmdlet.SessionState.PSVariable.GetValue($Param.Name) -Force
                (get-item $ParamDataFile -force).Attributes += 'Hidden'
            }
        }
    }
}

function Read-ScriptParam {
    [CmdletBinding()]
    param (
        # Specifies a path to one or more locations.
        [Parameter(Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Path to one or more locations.")]
        [Alias("PSPath")]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $ScriptPath = $MyInvocation.PSCommandPath,
        [switch]
        $NoSaveParameters
    )

    begin {
        Write-Log -Level VERBOSE -Message "BEGIN"
        $NonInteractive = Test-IsNonInteractiveShell
        Write-Log -Level WARNING -Message "Non Interactive : $NonInteractive"
    }

    process {
        $script = Get-ChildItem -Path $ScriptPath
        $scriptParameters = (Get-Command $Script).Parameters.GetEnumerator() | Where-Object { $_.Key -notin ([System.Management.Automation.Cmdlet]::CommonParameters) }
        $MaxLength = $scriptParameters.Key | Sort-Object -Property Length | Select-Object -Last 1 | Select-Object -ExpandProperty Length
        $ast = (Get-Command $Script).ScriptBlock.Ast
        $paramtoremove = @()
        foreach ($item in $scriptParameters) {
            $Param = $item.Value
            $CurrentValue = $PSCmdlet.SessionState.PSVariable.GetValue($Param.Name)
            Set-Variable -Name $Param.Name $CurrentValue
            $ParamDefinition = ($ast.ParamBlock.Parameters | Where-Object { $_.Name.VariablePath -like $Param.Name })
            if ($ParamDefinition.DefaultValue) {
                $CurrentDefaultValue = (Invoke-Expression $ParamDefinition.DefaultValue) -As $ParamDefinition.StaticType
            }
            if ($CurrentValue -and $CurrentValue.ToString() -eq $CurrentDefaultValue.ToString()) {
                $paramtoremove += $Param.Name
            }
        }
        foreach ($item in $paramtoremove) {
            Remove-Variable -Name $Item -ErrorAction SilentlyContinue
            try {
                $PSCmdlet.SessionState.PSVariable.Remove($item)
            }
            catch {
            }
        }
        foreach ($item in $scriptParameters) {
            $Param = $item.Value
            Write-Log -Level Verbose -Message "Updating Parameter {0}" -Arguments $Param.Name
            # Get Help Message
            $Message = ($Param.Attributes | Where-Object { $_.TypeId -eq [System.Management.Automation.ParameterAttribute] }).HelpMessage
            # If default value defined in script retrieve expression and determine value
            $ParamDefinition = $ast.paramblock.parameters | Where-Object { $_.Name.ToString().Replace('$', '') -like $Param.Name }
            $default = Get-ParameterDefault ($ParamDefinition)
            $count = 0
            if ($default) {
                if ($Param.ParameterType -eq [securestring]) {
                    $defaultString = ''.PadRight($default.Length, '*')
                }
                else {
                    $defaultString = $default.ToString()
                }

            }
            else { $defaultString = '' }
            do {
                If (($Message)) { Wait-Logging }
                If (!($Message) -or $NonInteractive -or !($value = Read-Host -Prompt "$Message $SetString [$defaultString]" -AsSecureString:($Param.ParameterType -eq [securestring]))) { $value = $default }
                if ($value.GetType() -eq [securestring] -and $value.Length -eq 0) {
                    $value = $default
                }
                if ($Param.SwitchParameter) { $type = [bool] } else { $type = $Param.ParameterType }
                $value = $value -as $type
                $count++
            } until ((Test-ParameterValidation $Param $value) -or $count -gt 3)
            if ($count -gt 3) {
                throw [System.Management.Automation.ValidationMetadataException]::new("$($Param.Name) -> $value")
            }
            $PSCmdlet.SessionState.PSVariable.Set($Param.Name, $Value)
            Set-Variable -Name $Param.Name -Value $value
            Write-Log -Level INFO "Arg:{0} = <{1}>" -Arguments $Param.Name.PadRight($MaxLength, " "), $Value
            if (($value -ne $default -or (Test-ParameterIsEnvironmentValue $ParamDefinition)) -and !($NoSaveParameters)) {
                Write-Log -Level WARNING -Message "Saving new default value for {0}" -Arguments $Param.Name
                Save-ParameterValue ($ParamDefinition)
            }
            if ($param.ParameterType -eq [System.IO.DirectoryInfo] -and !($value.exists)) {
                $f = New-Item -Path $value.FullName -ItemType Directory -Force
                Write-Log -Level WARNING -Message "Creating Folder {0}" -Arguments $f.FullName
            }
        }
    }

    end {
    }
}

Function Remove-DBTableData {

    <#
    .SYNOPSIS
        Runs a script containing statements supported by the SQL Server SQLCMD utility.
 
    .DESCRIPTION
        The Invoke-Sqlcmd cmdlet runs a script containing the languages and commands supported by the SQL Server SQLCMD utility.
 
        The commands supported are Transact-SQL statements and the subset of the XQuery syntax that is supported by the database engine.
 
        This cmdlet also accepts many of the commands supported natively by SQLCMD, such as GO and QUIT.
 
        This cmdlet also accepts the SQLCMD scripting variables, such as SQLCMDUSER. By default, this cmdlet does not set SQLCMD scripting variables.
 
        This cmdlet does not support the use of commands that are primarily related to interactive script editing.
 
        The commands not supported include :!!, :connect, :error, :out, :ed, :list, :listvar, :reset, :perftrace, and :serverlist.
 
        When this cmdlet is run, the first result set that the script returns is displayed as a formatted table.
 
        If subsequent result sets contain different column lists than the first, those result sets are not displayed.
 
        If subsequent result sets after the first set have the same column list, their rows are appended to the formatted table that contains the rows that were returned by the first result set.
 
        You can display SQL Server message output, such as those that result from the SQL PRINT statement, by specifying the Verbose parameter.
 
    .PARAMETER AbortOnError
        Indicates that this cmdlet stops the SQL Server command and returns an error level to the Windows PowerShell ERRORLEVEL variable if this cmdlet encounters an error.
 
        The error level returned is 1 if the error has a severity higher than 10, and the error level is 0 if the error has a severity of 10 or less.
 
        If the ErrorLevel parameter is also specified, this cmdlet returns 1 only if the error message severity is also equal to or higher than the value specified for ErrorLevel.
 
    .PARAMETER AccessToken
        A valid access token to be used to authenticate to SQL Server, in alternative to user/password or Windows Authentication.
 
        This can be used, for example, to connect to `SQL Azure DB` and `SQL Azure Managed Instance` using a `Service Principal` or a `Managed Identity` (see references at the bottom of this page)
 
        Do not specify UserName , Password , or Credential when using this parameter.
 
    .PARAMETER ConnectionString
        Specifies a connection string to connect to the server.
 
    .PARAMETER ConnectionTimeout
        Specifies the number of seconds when this cmdlet times out if it cannot successfully connect to an instance of the Database Engine. The timeout value must be an integer value between 0 and 65534. If 0 is specified, connection attempts do not time out.
 
    .PARAMETER Credential
        The PSCredential object whose Username and Password fields will be used to connect to the SQL instance.
 
    .PARAMETER Database
        Specifies the name of a database. This cmdlet connects to this database in the instance that is specified in the ServerInstance parameter.
 
        If the Database parameter is not specified, the database that is used depends on whether the current path specifies both the SQLSERVER:\SQL folder and a database name. If the path specifies both the SQL folder and a database name, this cmdlet connects to the database that is specified in the path. If the path is not based on the SQL folder, or the path does not contain a database name, this cmdlet connects to the default database for the current login ID. If you specify the IgnoreProviderContext parameter switch, this cmdlet does not consider any database specified in the current path, and connects to the database defined as the default for the current login ID.
 
    .PARAMETER DedicatedAdministratorConnection
        Indicates that this cmdlet uses a Dedicated Administrator Connection (DAC) to connect to an instance of the Database Engine.
 
        DAC is used by system administrators for actions such as troubleshooting instances that will not accept new standard connections.
 
        The instance must be configured to support DAC.
 
        If DAC is not enabled, this cmdlet reports an error and will not run.
 
    .PARAMETER DisableCommands
        Indicates that this cmdlet turns off some sqlcmd features that might compromise security when run in batch files.
 
        It prevents Windows PowerShell variables from being passed in to the Invoke-Sqlcmd script.
 
        The startup script specified in the SQLCMDINI scripting variable is not run.
 
    .PARAMETER DisableVariables
        Indicates that this cmdlet ignores sqlcmd scripting variables. This is useful when a script contains many INSERT statements that may contain strings that have the same format as variables, such as $(variable_name).
 
    .PARAMETER EncryptConnection
        Indicates that this cmdlet uses Secure Sockets Layer (SSL) encryption for the connection to the instance of the Database Engine specified in the ServerInstance parameter.
 
        If this parameter is specified, SSL encryption is used.
 
        If you do not specify this parameter, specified encryption is not used.
 
    .PARAMETER ErrorLevel
        Specifies that this cmdlet display only error messages whose severity level is equal to or higher than the value specified. All error messages are displayed if this parameter is not specified or set to 0. Database Engine error severities range from 1 to 24.
 
    .PARAMETER HostName
        Specifies a workstation name. The workstation name is reported by the sp_who system stored procedure and in the hostname column of the sys.processes catalog view. If this parameter is not specified, the default is the name of the computer on which Invoke-Sqlcmd is run. This parameter can be used to identify different Invoke-Sqlcmd sessions.
 
    .PARAMETER IgnoreProviderContext
        Indicates that this cmdlet ignores the database context that was established by the current SQLSERVER:\SQL path. If the Database parameter is not specified, this cmdlet uses the default database for the current login ID or Windows account.
 
    .PARAMETER IncludeSqlUserErrors
        Indicates that this cmdlet returns SQL user script errors that are otherwise ignored by default. If this parameter is specified, this cmdlet matches the default behavior of the sqlcmd utility.
 
    .PARAMETER InputFile
        Specifies a file to be used as the query input to this cmdlet. The file can contain Transact-SQL statements, XQuery statements, and sqlcmd commands and scripting variables. Specify the full path to the file. Spaces are not allowed in the file path or file name. The file is expected to be encoded using UTF-8.
 
        You should only run scripts from trusted sources. Ensure all input scripts are secured with the appropriate NTFS permissions.
 
    .PARAMETER MaxBinaryLength
        Specifies the maximum number of bytes returned for columns with binary string data types, such as binary and varbinary. The default value is 1,024 bytes.
 
    .PARAMETER MaxCharLength
        Specifies the maximum number of characters returned for columns with character or Unicode data types, such as char, nchar, varchar, and nvarchar. The default value is 4,000 characters.
 
    .PARAMETER NewPassword
        Specifies a new password for a SQL Server Authentication login ID. This cmdlet changes the password and then exits. You must also specify the Username and Password parameters, with Password that specifies the current password for the login.
 
    .PARAMETER OutputAs
        Specifies the type of the results this cmdlet gets.
 
        If you do not specify a value for this parameter, the cmdlet sets the value to DataRows.
 
    .PARAMETER OutputSqlErrors
        Indicates that this cmdlet displays error messages in the Invoke-Sqlcmd output.
 
    .PARAMETER Password
        Specifies the password for the SQL Server Authentication login ID that was specified in the Username parameter. Passwords are case-sensitive. When possible, use Windows Authentication. Do not use a blank password, when possible use a strong password.
 
        If you specify the Password parameter followed by your password, the password is visible to anyone who can see your monitor.
 
        If you code Password followed by your password in a .ps1 script, anyone reading the script file will see your password.
 
        Assign the appropriate NTFS permissions to the file to prevent other users from being able to read the file.
 
    .PARAMETER QueryTimeout
        Specifies the number of seconds before the queries time out. If a timeout value is not specified, the queries do not time out. The timeout must be an integer value between 1 and 65535.
 
    .PARAMETER ServerInstance
        Specifies a character string or SQL Server Management Objects (SMO) object that specifies the name of an instance of the Database Engine. For default instances, only specify the computer name: MyComputer. For named instances, use the format ComputerName\InstanceName.
 
    .PARAMETER SeverityLevel
        Specifies the lower limit for the error message severity level this cmdlet returns to the ERRORLEVEL Windows PowerShell variable.
 
        This cmdlet returns the highest severity level from the error messages generated by the queries it runs, provided that severity is equal to or higher than specified in the SeverityLevel parameter.
 
        If SeverityLevel is not specified or set to 0, this cmdlet returns 0 to ERRORLEVEL.
 
        The severity levels of Database Engine error messages range from 1 to 24.
 
        This cmdlet does not report severities for informational messages that have a severity of 10
 
    .PARAMETER SuppressProviderContextWarning
        Indicates that this cmdlet suppresses the warning that this cmdlet has used in the database context from the current SQLSERVER:\SQL path setting to establish the database context for the cmdlet.
 
    .PARAMETER Username
        Specifies the login ID for making a SQL Server Authentication connection to an instance of the Database Engine.
 
        The password must be specified through the Password parameter.
 
        If Username and Password are not specified, this cmdlet attempts a Windows Authentication connection using the Windows account running the Windows PowerShell session. When possible, use Windows Authentication.
 
    .PARAMETER Variable
        Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable.
 
        Use a Windows PowerShell array to specify multiple variables and their values.
 
    .EXAMPLE
        Get-DBTableRowCount -Database 'DatabaseName'
        Returns the row counts for all tables in the database DatabaseName
    .EXAMPLE
        Get-DBTableRowCount -Database 'MNPCalendar' -TableName 'Table'
        Returns the row count for the Table
    .OUTPUTS
 
    .LINK
        SQLServer_Cmdlets
 
    .LINK
        https://docs.microsoft.com/azure/azure-sql/database/authentication-aad-service-principal
 
    .LINK
        Service Principal
 
    .LINK
        https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi
 
    .LINK
        Managed Identity
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "")]
    [CmdletBinding(DefaultParameterSetName = 'ByConnectionParameters')]
    param(
        [Parameter(ParameterSetName = 'ByConnectionParameters', ValueFromPipeline = $true)]
        [psobject]
        ${ServerInstance},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Database},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [switch]
        ${EncryptConnection},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Username},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [ValidateNotNullOrEmpty()]
        [string]
        ${AccessToken},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Password},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [ValidateNotNullOrEmpty()]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${Credential},

        [ValidateRange(0, 65535)]
        [int]
        ${QueryTimeout},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [int]
        ${ConnectionTimeout},

        [ValidateRange(-1, 255)]
        [int]
        ${ErrorLevel},

        [ValidateRange(-1, 25)]
        [int]
        ${SeverityLevel},

        [ValidateRange(1, 2147483647)]
        [int]
        ${MaxCharLength},

        [ValidateRange(1, 2147483647)]
        [int]
        ${MaxBinaryLength},

        [switch]
        ${AbortOnError},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [switch]
        ${DedicatedAdministratorConnection},

        [switch]
        ${DisableCommands},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [ValidateNotNullOrEmpty()]
        [string]
        ${HostName},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [string]
        ${NewPassword},

        [string]
        ${TableName},

        [ValidateNotNullOrEmpty()]
        [string]
        ${InputFile},

        [bool]
        ${OutputSqlErrors},

        [switch]
        ${IncludeSqlUserErrors},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [switch]
        ${SuppressProviderContextWarning},

        [Parameter(ParameterSetName = 'ByConnectionParameters')]
        [switch]
        ${IgnoreProviderContext},

        [Alias('As')]
        [Microsoft.SqlServer.Management.PowerShell.OutputType]
        ${OutputAs},

        [Parameter(ParameterSetName = 'ByConnectionString', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        ${ConnectionString})

    begin {
        DATA Query {
            @"
            DECLARE @tbl varchar(30) = '`$(TableName)'
            DECLARE @dynSQL varchar(50)
            SET @dynSQL = 'TRUNCATE TABLE ' + @tbl
            EXEC (@dynSQL)
"@

        }
        if ($TableName) {
            $Variable = "TableName=$TableName"
        }
        else {
            $Variable = "TableName=%"
        }


        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $PSBoundParameters.Remove('TableName') | Out-Null
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('SqlServer\Invoke-Sqlcmd', [System.Management.Automation.CommandTypes]::Cmdlet)
            $scriptCmd = { & $wrappedCmd @PSBoundParameters -Query $Query -Variable $Variable }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        }
        catch {
            throw
        }

    }

    process {
        try {
            $steppablePipeline.Process($_)
        }
        catch {
            throw
        }
    }

    end {
        try {
            $steppablePipeline.End()
        }
        catch {
            throw
        }
    }
}
function Restart-ComputerAndContinue {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter()]
        [String]
        $LogParameterName = 'LogFile',
        # Parameter to hold arguments to pass to script
        [Parameter()]
        [String[]]
        $ScriptArguments
    )
    $script = Get-ChildItem -Path $MyInvocation.PSCommandPath
    $scriptParameters = (Get-Command $Script).Parameters
    [System.Collections.ArrayList]$Arguments = @("&", "'$($script.FullName)'")
    if ($scriptParameters.ContainsKey($LogParameterName)) {
        $null = $Arguments.Add("-$LogParameterName '$((Get-LoggingTarget -Name File).Path)'")
    }
    foreach ($argument in $ScriptArguments) {
        $null = $Arguments.Add("$argument")
    }
    if ($PSVersionTable.PSEdition -eq 'Core') {
        $pstart = "pwsh.exe"
    }
    else {
        $pstart = "powershell.exe"
    }
    $actionParameters = @{
        Execute  = $pstart
        Argument = "-NonInteractive -WindowStyle Normal -NoLogo -NoProfile -NoExit -Command ""$($Arguments -join ' ')"""
    }
    $Trigger = (New-ScheduledTaskTrigger -AtLogOn -User "$($Env:USERDOMAIN)\\$($Env:USERNAME)" -RandomDelay (New-Timespan -Seconds 10))
    $Trigger.Delay = 'PT30S'
    $ScheduledTaskParameters = @{
        TaskName = "Logon Script - $($script.BaseName)"
        Action   = (New-ScheduledTaskAction @actionParameters)
        Trigger  = $Trigger
        Settings = (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -RunOnlyIfNetworkAvailable)
        RunLevel = [Microsoft.PowerShell.Cmdletization.GeneratedTypes.ScheduledTask.RunLevelEnum]'Highest'
    }
    if ((Test-PendingReboot)) {
        if ($PSCmdlet.ShouldProcess("ScheduledTask [$($ScheduledTaskParameters.TaskName)]", "Create")) {
            try {
                $null = Register-ScheduledTask @ScheduledTaskParameters -Force
                Write-Log -Level WARNING -Message "Created Scheduled Task {0}" -Arguments $ScheduledTaskParameters.TaskName
                Write-Log -Level WARNING "Restarting Computer"
                Wait-Logging
                if (!(Test-Interactive)) {
                    Restart-Computer -Force
                }
                else {
                    Write-Output "Press any key to reboot (ESC to ignore)..."
                    $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
                    if ($key.VirtualKeyCode -ne 27) {
                        Restart-Computer -Force
                    }
                    else {
                        Write-Log -Level Warning -Message "Restart Cancelled by user"
                    }

                }

            }
            catch {
                Write-Log -Level ERROR "Unable to create task {0}" -ExceptionInfo $_ -Arguments $ScheduledTaskParameters.TaskName
                throw $_
            }
        }
    }
    else {
        Write-Log -Level DEBUG -Message "No Restart Required"
        $Tasks = Get-ScheduledTask | Where-Object { ($_ | Select-Object -ExpandProperty Actions | Where-Object { $_.Arguments -like "*$($Script.FullName)*" -and ($_.Execute -like '*powershell*' -or $_.Execute -like '*pwsh*') }) }
        foreach ($task in $Tasks) {
            Write-Log -Level WARNING -Message "Removing Scheduled Task [{0}]" -Arguments $task.TaskName
            $task | Unregister-ScheduledTask -Confirm:$false
        }
    }
}
function Set-CredentialManagerCredential {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
    param (
        [String]$Target,
        [Parameter(ParameterSetName='USER+PASS')]
        [String]$User='User',
        [Parameter(ParameterSetName='USER+PASS')]
        [SecureString]$Pass,
        [Parameter(ParameterSetName='CREDENTIAL')]
        [PSCredential]$UserCredential,
        [String]$Comment
    )
    IF($PSCmdlet.ParameterSetName -eq 'CREDENTIAL'){
        $User = $UserCredential.UserName
        $Pass = $UserCredential.Password
    }
    $TextPass = ConvertTo-PlainText $Pass
    $Target = if ($User){"$User@$Target"} else {$Target}
    ConvertFrom-CredMan(credman -AddCred -Target $Target -User $User -Pass $TextPass -Comment "$Comment")
}
function Set-EnvironmentVariable {
    #.Synopsis
    # Set an environment variable at the highest scope possible
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Position = 0)]
        [String]$Name,

        [Parameter(Position = 1)]
        [String]$Value,

        [System.EnvironmentVariableTarget]
        $Scope = "Machine",

        [Switch]$FailFast
    )
    Write-Information "Set-EnvironmentVariable $Name $Value -Scope $Machine" -Tags "Trace", "Enter"
    if ((Get-Content "ENV:$Name" -ErrorAction SilentlyContinue) -ne $Value) {
        Set-Content "ENV:$Name" $Value
        Write-Log -Level Verbose -Message "Set {2} Environment Variable {0} = {1}" -Arguments $Name,$Value,'Session'
    }

    $Success = $False
    do {
        try {
            if ($PSCmdlet.ShouldProcess("ENV:$Name $Scope", "Set $Value") -and ([System.Environment]::GetEnvironmentVariable($Name,$Scope) -ne $Value)) {
                [System.Environment]::SetEnvironmentVariable($Name, $Value, $Scope)
                Write-Log -Level WARNING -Message "Set {2} Environment Variable {0} = {1}" -Arguments $Name,$Value,$Scope
            }
            $Success = $True
        }
        catch [System.Security.SecurityException] {
            if ($FailFast) {
                $PSCmdlet.ThrowTerminatingError( (New-Object System.Management.Automation.ErrorRecord (
                            New-Object AccessViolationException "Can't set environment variable in $Scope scope"
                        ), "FailFast:$Scope", "PermissionDenied", $Scope) )
            }
            else {
                Write-Log -Level WARNING -Message "Cannot set environment variables in the $Scope scope"
            }
            $Scope = [int]$Scope - 1
        }
    } while (!$Success -and $Scope -gt "Process")

    Write-Information "Set-EnvironmentVariable $Name $Value -Scope $Machine" -Tags "Trace", "Exit"
}
function Start-DefaultLogging {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        $LogFile,
        $LogPath,
        $LogLevel = 'INFO'
    )
    $script = Get-ChildItem -Path $MyInvocation.PSCommandPath
    If (!($LogPath)) {
        $LogPath = Join-Path $env:ProgramData $script.BaseName
    }
    else {
        $CurrentFileLogging = (Get-LoggingTarget)['File']
    }
    if ($PSCmdlet.ShouldProcess("$LogPath", "CreateFolder")) {
        $null = New-Item $LogPath -ItemType Directory -Force
    }
    If (!($LogFile) ) {
        $wildcardBasePath = "$(($script).BaseName) $( Get-Date -Format "yyyy-MM-dd") *.log"
        If (Test-Path $LogPath) {
            $FileCount = ([System.IO.Directory]::GetFiles($logPath, $wildcardBasePath)).Count
        }
        else {
            $FileCount = 0
        }
        $logFileNumber = "{0:d2}" -f $FileCount
        $logFile = Join-Path $logPath ($wildcardBasePath.Replace('*', $logFileNumber))
    }
    if ($PSCmdlet.ShouldProcess("Logging", "Configure")) {
        Add-LoggingLevel -LevelName 'VERBOSE' -Level 5
        Set-LoggingDefaultLevel $LogLevel
        Add-LoggingTarget -Name Console -Configuration @{ColorMapping = @{
                'VERBOSE' = 'Gray'
                'DEBUG'   = 'Blue'
                'INFO'    = 'Green'
                'WARNING' = 'Yellow'
                'ERROR'   = 'Red'
            }
        }
        if ($CurrentFileLogging) {
            Wait-Logging
            If (Test-Path $CurrentFileLogging.Path) {
                Get-Content $CurrentFileLogging.Path | Add-Content $LogFile -Force
                Remove-Item $CurrentFileLogging.Path
            }
            $CurrentFileLogging.Path = $LogFile
            Add-LoggingTarget -Name File -Configuration $CurrentFileLogging
        }
        else {
            Add-LoggingTarget -Name File -Configuration @{Path = $logFile }
        }
        If (!($CurrentFileLogging)) {
            if (Test-Interactive) {
                $Type = "Interactive"
            }
            else {
                $Type = "Non-Interactive"
            }
            Write-Log -Level DEBUG -Message 'Starting Script {0} {1}' -Arguments $Script.Name, $Type
        }
        Write-Log -Level WARNING -Message "Logging to file {0}" -Arguments (Get-LoggingTarget -Name File).Path
    }
}
function Test-Interactive {
    !([bool]([Environment]::GetCommandLineArgs() -like '-noni*') -and ! ($env:TERM_PROGRAM -eq 'vscode'))
}
function Test-IsAdmin {

    ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")

}