MOA_Module.psm1

using namespace System.Collections.Generic
using namespace System.Security.AccessControl
using namespace System.Security.Cryptography.X509Certificates

function ConvertFrom-Html {
    param([System.String] $html)
   
    # remove line breaks, replace with spaces
    $html = $html -replace '(`r|`n|`t)', ' '
    # write-verbose 'removed line breaks: `n`n$html`n'
   
    # remove invisible content
    @('head', 'style', 'script', 'object', 'embed', 'applet', 'noframes', 'noscript', 'noembed') | ForEach-Object {
     $html = $html -replace '<$_[^>]*?>.*?</$_>', ''
    }
    # write-verbose 'removed invisible blocks: `n`n$html`n'
   
    # Condense extra whitespace
    $html = $html -replace '( )+', ' '
    # write-verbose 'condensed whitespace: `n`n$html`n'
   
    # Add line breaks
    @('div','p','blockquote','h[1-9]') | ForEach-Object { $html = $html -replace '</?$_[^>]*?>.*?</$_>', ('`n' + '$0' )} 
    # Add line breaks for self-closing tags
    @('div','p','blockquote','h[1-9]','br') | ForEach-Object { $html = $html -replace '<$_[^>]*?/>', ('$0' + '`n')} 
    # write-verbose 'added line breaks: `n`n$html`n'
   
    #strip tags
    $html = $html -replace '<[^>]*?>', ''
    # write-verbose 'removed tags: `n`n$html`n'
     
    # replace common entities
    @( 
     @('&amp;bull;', ' * '),
     @('&amp;lsaquo;', '<'),
     @('&amp;rsaquo;', '>'),
     @('&amp;(rsquo|lsquo);', "'"),
     @('&amp;(quot|ldquo|rdquo);', "'"),
     @('&amp;trade;', 'tm'),
     @('&amp;frasl;', '/'),
     @('&amp;(quot|#34|#034|#x22);', "'"),
     @('&amp;(amp|#38|#038|#x26);', '&amp;'),
     @('&amp;(lt|#60|#060|#x3c);', '<'),
     @('&amp;(gt|#62|#062|#x3e);', '>'),
     @('&amp;(copy|#169);', '(c)'),
     @('&amp;(reg|#174);', '(r)'),
     @('&amp;nbsp;', ' '),
     @('&amp;(.{2,6});', '')
    ) | ForEach-Object { $html = $html -replace $_[0], $_[1] }
    # write-verbose 'replaced entities: `n`n$html`n'
   
    return $html
   
}

function Get-IpRange {
<#
.SYNOPSIS
    Given a subnet in CIDR format, get all of the valid IP addresses in that range.
.DESCRIPTION
    Given a subnet in CIDR format, get all of the valid IP addresses in that range.
.PARAMETER Subnets
    The subnet written in CIDR format 'a.b.c.d/#' and an example would be '192.168.1.24/27'. Can be a single value, an
    array of values, or values can be taken from the pipeline.
.EXAMPLE
    Get-IpRange -Subnets '192.168.1.24/30'
    
    192.168.1.25
    192.168.1.26
.EXAMPLE
    (Get-IpRange -Subnets '10.100.10.0/24').count
    
    254
.EXAMPLE
    '192.168.1.128/30' | Get-IpRange
    
    192.168.1.129
    192.168.1.130
.NOTES
    Inspired by https://gallery.technet.microsoft.com/PowerShell-Subnet-db45ec74
    
    * Added comment help
#>


    [CmdletBinding(ConfirmImpact = 'None')]
    Param(
        [Parameter(Mandatory, HelpMessage = 'Please enter a subnet in the form a.b.c.d/#', ValueFromPipeline, Position = 0)]
        [string[]] $Subnets
    )

    begin {
        Write-Verbose -Message "Starting [$($MyInvocation.Mycommand)]"
    }

    process {
        foreach ($subnet in $subnets) {
            if ($subnet -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}$') {
                #Split IP and subnet
                $IP = ($Subnet -split '\/')[0]
                [int] $SubnetBits = ($Subnet -split '\/')[1]
                if ($SubnetBits -lt 7 -or $SubnetBits -gt 30) {
                    Write-Error -Message 'The number following the / must be between 7 and 30'
                    break
                }
                #Convert IP into binary
                #Split IP into different octects and for each one, figure out the binary with leading zeros and add to the total
                $Octets = $IP -split '\.'
                $IPInBinary = @()
                foreach ($Octet in $Octets) {
                    #convert to binary
                    $OctetInBinary = [convert]::ToString($Octet, 2)
                    #get length of binary string add leading zeros to make octet
                    $OctetInBinary = ('0' * (8 - ($OctetInBinary).Length) + $OctetInBinary)
                    $IPInBinary = $IPInBinary + $OctetInBinary
                }
                $IPInBinary = $IPInBinary -join ''
                #Get network ID by subtracting subnet mask
                $HostBits = 32 - $SubnetBits
                $NetworkIDInBinary = $IPInBinary.Substring(0, $SubnetBits)
                #Get host ID and get the first host ID by converting all 1s into 0s
                $HostIDInBinary = $IPInBinary.Substring($SubnetBits, $HostBits)
                $HostIDInBinary = $HostIDInBinary -replace '1', '0'
                #Work out all the host IDs in that subnet by cycling through $i from 1 up to max $HostIDInBinary (i.e. 1s stringed up to $HostBits)
                #Work out max $HostIDInBinary
                $imax = [convert]::ToInt32(('1' * $HostBits), 2) - 1
                $IPs = @()
                #Next ID is first network ID converted to decimal plus $i then converted to binary
                For ($i = 1 ; $i -le $imax ; $i++) {
                    #Convert to decimal and add $i
                    $NextHostIDInDecimal = ([convert]::ToInt32($HostIDInBinary, 2) + $i)
                    #Convert back to binary
                    $NextHostIDInBinary = [convert]::ToString($NextHostIDInDecimal, 2)
                    #Add leading zeros
                    #Number of zeros to add
                    $NoOfZerosToAdd = $HostIDInBinary.Length - $NextHostIDInBinary.Length
                    $NextHostIDInBinary = ('0' * $NoOfZerosToAdd) + $NextHostIDInBinary
                    #Work out next IP
                    #Add networkID to hostID
                    $NextIPInBinary = $NetworkIDInBinary + $NextHostIDInBinary
                    #Split into octets and separate by . then join
                    $IP = @()
                    For ($x = 1 ; $x -le 4 ; $x++) {
                        #Work out start character position
                        $StartCharNumber = ($x - 1) * 8
                        #Get octet in binary
                        $IPOctetInBinary = $NextIPInBinary.Substring($StartCharNumber, 8)
                        #Convert octet into decimal
                        $IPOctetInDecimal = [convert]::ToInt32($IPOctetInBinary, 2)
                        #Add octet to IP
                        $IP += $IPOctetInDecimal
                    }
                    #Separate by .
                    $IP = $IP -join '.'
                    $IPs += $IP
                }
                Write-Output -InputObject $IPs
            } else {
                Write-Error -Message "Subnet [$subnet] is not in a valid format"
            }
        }
    }

    end {
        Write-Verbose -Message "Ending [$($MyInvocation.Mycommand)]"
    }
}

function ConvertFrom-INI() {
    [CmdletBinding()]   
    Param(
        [Parameter(
            ValueFromPipeline
        )]
        [string[]]$InputObject,
        [string]$Path,
        [switch]$UseHeadings
    )
        
    $Settings = [List[PsObject]]::New()

    if ($UseHeadings) {
        $InputObject = Get-Content -Path $Path
        foreach ($Line in $InputObject) {
            if ($line.StartsWith('[')) {
                $Header=$Line.Replace("[",'').Replace("]",'')
                if ($PrevHeader) {
                    if ($Header -ne $PrevHeader) {
                        $Section = [PSCustomObject]@{
                           $PrevHeader = $HeaderSettings.ToArray()

                        }
                        $Settings.Add($Section)
                        $HeaderSettings = [List[PsObject]]::New()
                        $PrevHeader = $Header
                    }
                }else {
                    $PrevHeader = $Header
                    $HeaderSettings = [List[PsObject]]::New()
                }
            } else {
                if ($Line.Length -gt 0) {
                    $values = $line | ConvertFrom-StringData
                    $Setting = [PSCustomObject]@{
                        [string]$values.Keys[0] = [string]$values.values[0]
                    }
                    $HeaderSettings.Add($Setting)
                }
            }            
        }
        $Section = [PSCustomObject]@{
            $PrevHeader = $HeaderSettings.ToArray()
        }
        $Settings.Add($Section)
    } else {
        $Settings = Get-Content -Path $Path | Where-Object {$_ -notmatch "^\["} | ConvertFrom-StringData
        foreach ($Setting in $Settings) {
            $Setting = [PSCustomObject]@{
                [string]$Setting.Keys[0] = [string]$Setting.values[0]
            }
            $Settings.Add($setting)
        }
    }

    return $Settings.ToArray()
}

function Add-Ace() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(Mandatory)]
        [string]$Path,
        [Parameter(Mandatory)]
        [string]$Identity,
        [Parameter(Mandatory)]       
        [String[]]$FileSystemRights,        
        [string[]]$InheritanceFlags = 'None',
        [string[]]$PropagationFlags = @('None'),
        [ValidateSet('Allow','Deny')]
        [string]$AccessControlType = 'Allow',
        [switch]$Recurse
    )

    # Test Operating system
    If (-Not $IsWindows) {
        Write-Host "This function is only available on Windows"
        return
    }

    If (Test-Path $Path) {
        try {
            $Acl = Get-Acl -Path $Path
        } catch {
            throw $_
        }

        If ($InheritanceFlags) {
            $FileSystemAccessRule = [FileSystemAccessRule]::New($Identity,$FileSystemRight,$InheritanceFlags,$PropagationFlags,$AccessControlType)
        } else {
            $FileSystemAccessRule = [FileSystemAccessRule]::New($Identity,$FileSystemRight,$AccessControlType)
        }

        $Acl.SetAccessRule($FileSystemAccessRule)

        try {
            Set-Acl -Path $Path -AclObject $Acl -Verbose

            if ($Recurse) {
                $response = Read-Host -Prompt "Replace permissions on on all child objects [y/N]: "
                If ($response -eq 'y') {
                    $Children = Get-ChildItem -Path -Recurse
                    foreach ($Child in $Children) {
                        set-acl -Path $Child.FullName -AclObject $Acl -verbose
                    }
                } else {
                    Write-Host "Permissions not applied to child objects"
                }
            }

        } catch {
            throw $_
        }
    }
}
Function Import-X509Certificate () {
    [CmdletBinding()]
    Param(
        [string]$StoreName = "My",
        [ValidateSet('CurrentUser','LocalMachine')]
        [string]$Scope = 'CurrentUser',
        [Parameter(Mandatory)]
        [string]$CertificatePath,
        [securestring]$PassPhrase
    )

    $Store = [X509Store]::new($StoreName, $Scope, 'ReadWrite')
    $Store.Add([X509Certificate2]::New($CertificatePath,$PassPhrase,[X509KeyStorageFlags]::PersistKeySet))
    $Store.Dispose()
}

function ConvertTo-DataTable {
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [object[]]$InputObject
    )
    begin {
        $dataTable = New-Object System.Data.DataTable
    }
    process {
        foreach ($object in $InputObject) {
            if ($dataTable.Columns.Count -eq 0) {
                $object | Get-Member -MemberType Properties | ForEach-Object {
                    $dataTable.Columns.Add($_.Name, $_.Type)
                }
            }
            $dataRow = $dataTable.NewRow()
            foreach ($column in $dataTable.Columns) {
                $dataRow[$column.ColumnName] = $object."$($column.ColumnName)"
            }
            $dataTable.Rows.Add($dataRow)
        }
    }
    end {
        $dataTable
    }
}

function Show-ProgressBar {
    param (
        [Parameter(Mandatory = $false)]
        [int]$PercentComplete = 100,
        
        [Parameter(Mandatory = $false)]
        [int]$BarLength = 60,
        
        [Parameter(Mandatory = $false)]
        [char]$BarChar = '=',
        
        [Parameter(Mandatory = $false)]
        [string]$Activity = "Processing",
        
        [Parameter(Mandatory = $false)]
        [string]$Status = "",
        
        [Parameter(Mandatory = $false)]
        [switch]$Completed,
        
        [Parameter(Mandatory = $false)]
        [switch]$Spinner,
        
        [Parameter(Mandatory = $false)]
        [System.ConsoleColor]$ForegroundColor = [Console]::ForegroundColor,
        
        [Parameter(Mandatory = $false)]
        [System.ConsoleColor]$BarForegroundColor,
        
        [Parameter(Mandatory = $false)]
        [System.ConsoleColor]$BarBackgroundColor
    )

    # We are performing some parameter checking here
    # we are doing this here because Parameter sets are two confusing and don't give meaningful error messages.
    if ($PercentComplete -lt 0 -or $PercentComplete -gt 100) {
        Write-Host "PercentComplete must be between 0 and 100." -ForegroundColor Red
        return
    }

    if ($BarLength -lt 1) {
        Write-Host "BarLength must be at least 1." -ForegroundColor Red
        return
    }

    if ($BarForegroundColor -and $BarBackgroundColor -and $BarForegroundColor -eq $BarBackgroundColor) {
        Write-Host "BarForegroundColor and BarBackgroundColor cannot be the same." -ForegroundColor Red
        return
    }
    
    # Static variable to keep track of spinner state
    if (-not [bool]::TryParse($script:spinnerInitialized, [ref]$null)) {
        $script:spinnerInitialized = $true
        $script:spinnerIndex = 0
    }
    
    # Spinner characters in correct rotation order
    $spinnerChars = @('-', '\', '|', '/', '-', '\', '|', '/')
    
    # Check if only the -Completed switch was provided (with default values for other parameters)
    $onlyCompletedProvided = $Completed -and 
                             $PSBoundParameters.Count -eq 1 -and
                             $PercentComplete -eq 100 -and
                             $BarLength -eq 60 -and
                             $BarChar -eq '=' -and
                             $Activity -eq "Processing" -and
                             $Status -eq "" -and
                             (-not $Spinner)
    
    # If only -Completed is specified, clear the progress bar line
    if ($onlyCompletedProvided) {
        # Create a blank line that overwrites the existing progress bar
        $clearLine = "`r" + " " * 200 + "`r"  # 200 spaces should be enough to clear most lines
        Write-ConsoleOnly $clearLine -NoNewline
        return
    }
    
    # Build the display string
    if ($Spinner) {
        if ($Completed) {
            # For completed spinners, we want to clear the line first to avoid residual characters
            $clearLine = "`r" + " " * 200 + "`r"  # 200 spaces should be enough to clear most lines
            Write-ConsoleOnly $clearLine -NoNewline
            
            # Then display the final state
            $finalChar = $spinnerChars[0]  # Use the first spinner character for completion
            
            # Record the previous status length to ensure we clear it properly
            $maxStatusLength = 0
            foreach ($char in $spinnerChars) {
                $tempStatus = "$Activity [$char]"
                if (-not [string]::IsNullOrWhiteSpace($Status)) {
                    $tempStatus += " - $Status"
                }
                $maxStatusLength = [Math]::Max($maxStatusLength, $tempStatus.Length)
            }
            
            # Add padding to ensure we clear the longest possible status message
            $padding = [string]::new(' ', $maxStatusLength + 20) # Extra padding to be safe
            Write-ConsoleOnly "`r$padding`r" -NoNewline
            
            if ($PSBoundParameters.ContainsKey('BarForegroundColor') -or $PSBoundParameters.ContainsKey('BarBackgroundColor')) {
                # Final state with custom colors
                $spinnerFg = if ($PSBoundParameters.ContainsKey('BarForegroundColor')) { $BarForegroundColor } else { $ForegroundColor }
                $spinnerBg = if ($PSBoundParameters.ContainsKey('BarBackgroundColor')) { $BarBackgroundColor } else { [Console]::BackgroundColor }
                
                # Display the prefix with main foreground color
                Write-ConsoleOnly "$Activity [" -ForegroundColor $ForegroundColor -NoNewline
                
                # Display the spinner character with its specific colors
                Write-ConsoleOnly "$finalChar" -ForegroundColor $spinnerFg -BackgroundColor $spinnerBg -NoNewline
                
                # Display the suffix with main foreground color
                Write-ConsoleOnly "]" -ForegroundColor $ForegroundColor -NoNewline
                
                # Add status if provided
                if (-not [string]::IsNullOrWhiteSpace($Status)) {
                    Write-ConsoleOnly " - $Status" -ForegroundColor $ForegroundColor
                } else {
                    Write-ConsoleOnly "" -ForegroundColor $ForegroundColor
                }
            }
            else {
                # Final state with default colors
                $finalDisplay = "$Activity [$finalChar]"
                if (-not [string]::IsNullOrWhiteSpace($Status)) {
                    $finalDisplay += " - $Status"
                }
                Write-ConsoleOnly $finalDisplay -ForegroundColor $ForegroundColor
            }
            
            return
        }
        
        # Get the current spinner character for animated spinner
        $currentSpinnerChar = $spinnerChars[$script:spinnerIndex]
        
        # Update spinner index for next call
        $script:spinnerIndex = ($script:spinnerIndex + 1) % $spinnerChars.Length
        
        # Create spinner display
        $displayStringPrefix = "$Activity ["
        $displayStringSuffix = "]"
        
        # Handle the spinner character separately to apply specific colors
        if ($PSBoundParameters.ContainsKey('BarForegroundColor') -or $PSBoundParameters.ContainsKey('BarBackgroundColor')) {
            # Prepare spinner character with specific colors
            $spinnerFg = if ($PSBoundParameters.ContainsKey('BarForegroundColor')) { $BarForegroundColor } else { $ForegroundColor }
            $spinnerBg = if ($PSBoundParameters.ContainsKey('BarBackgroundColor')) { $BarBackgroundColor } else { [Console]::BackgroundColor }
            
            # Display the prefix with main foreground color
            Write-ConsoleOnly "`r$displayStringPrefix" -ForegroundColor $ForegroundColor -NoNewline
            
            # Display the spinner character with its specific colors
            Write-ConsoleOnly "$currentSpinnerChar" -ForegroundColor $spinnerFg -BackgroundColor $spinnerBg -NoNewline
            
            # Display the suffix with main foreground color
            Write-ConsoleOnly "$displayStringSuffix" -ForegroundColor $ForegroundColor -NoNewline
            
            # Add status if provided
            if (-not [string]::IsNullOrWhiteSpace($Status)) {
                Write-ConsoleOnly " - $Status" -ForegroundColor $ForegroundColor -NoNewline
            }
        }
        else {
            # Use the default/specified foreground color for everything
            Write-ConsoleOnly "`r$Activity [$currentSpinnerChar]" -ForegroundColor $ForegroundColor -NoNewline
            
            # Add status if provided
            if (-not [string]::IsNullOrWhiteSpace($Status)) {
                Write-ConsoleOnly " - $Status" -ForegroundColor $ForegroundColor -NoNewline
            }
        }
        
        # If -Completed is specified with -Spinner, add a newline to finalize and show completion
        # This block was moved to the beginning of the Spinner section
    }
    else {
        # Regular progress bar
        # Ensure percent is within valid range
        $PercentComplete = [Math]::Max(0, [Math]::Min(100, $PercentComplete))
        
        # Calculate how many bar characters to display
        $completedChars = [Math]::Floor(($BarLength * $PercentComplete) / 100)
        
        # Check if we need to apply special colors to the bar
        $useSpecialBarColors = $PSBoundParameters.ContainsKey('BarForegroundColor') -or $PSBoundParameters.ContainsKey('BarBackgroundColor')
        
        if ($useSpecialBarColors) {
            # Write the first part of the string with main foreground color
            Write-ConsoleOnly "`r$Activity [" -ForegroundColor $ForegroundColor -NoNewline
            
            # Progress bar with special colors
            $barFg = if ($PSBoundParameters.ContainsKey('BarForegroundColor')) { $BarForegroundColor } else { $ForegroundColor }
            $barBg = if ($PSBoundParameters.ContainsKey('BarBackgroundColor')) { $BarBackgroundColor } else { [Console]::BackgroundColor }
            
            # Write the completed part of the bar with special colors
            if ($completedChars -gt 0) {
                $progressBar = [string]::new($BarChar, $completedChars)
                Write-ConsoleOnly $progressBar -ForegroundColor $barFg -BackgroundColor $barBg -NoNewline
            }
            
            # Write the remaining part of the bar with main foreground color and default background
            $remainingLength = $BarLength - $completedChars
            if ($remainingLength -gt 0) {
                $remainingBar = [string]::new(' ', $remainingLength)
                # Always use default background for the remaining part
                Write-ConsoleOnly $remainingBar -ForegroundColor $ForegroundColor -NoNewline
            }
            
            # Write the rest of the string with main foreground color
            $statusText = "] $PercentComplete%"
            if (-not [string]::IsNullOrWhiteSpace($Status)) {
                $statusText += " - $Status"
            }
            Write-ConsoleOnly $statusText -ForegroundColor $ForegroundColor -NoNewline
        }
        else {
            # Build the progress bar (standard version with no special colors)
            $progressBar = [string]::new($BarChar, $completedChars)
            $remainingBar = [string]::new(' ', $BarLength - $completedChars)
            
            # Build the display string for progress bar
            $displayString = "`r$Activity [$progressBar$remainingBar] $PercentComplete%"
            
            # Add status if provided
            if (-not [string]::IsNullOrWhiteSpace($Status)) {
                $displayString += " - $Status"
            }
            
            # Display the progress indicator with the specified foreground color
            Write-ConsoleOnly $displayString -ForegroundColor $ForegroundColor -NoNewline
        }
        
        # If -Completed is specified with custom parameters, add a newline to finalize and keep it visible
        if ($Completed) {
            Write-ConsoleOnly "" -ForegroundColor $ForegroundColor
        }
    }
    <#
    .SYNOPSIS
        Displays a progress bar in the console.
    .DESCRIPTION
        Displays a progress bar in the console. The progress bar can be used to show the progress of a task.
        The progress bar can be displayed in two modes:
        1. Normal progress bar: Shows a progress bar that fills up as the task progresses.
        2. Spinner: Shows a spinner that rotates to indicate that the task is in progress.
    .PARAMETER PercentComplete
        The percentage of the task that is complete. This value should be between 0 and 100.
    .PARAMETER BarLength
        The length of the progress bar. The default value is 60.
    .PARAMETER BarChar
        The character to use for the progress bar. The default value is '='.
    .PARAMETER Activity
        The activity that is being performed. The default value is "Processing".
    .PARAMETER Status
        Additional information about the task. This information is displayed next to the progress bar.
    .PARAMETER Completed
        Indicates that the task is complete. When this switch is used, the progress bar is filled to 100% and the completion message is displayed.
        If used with the -Spinner switch, the spinner will stop and the completion message will be displayed.
        if used with -Activity or -Status the barf remains on the console and the Activity and Status message is displayed,
        if used without any other parameters the progress bar is cleared.
    .PARAMETER Spinner
        Indicates that a spinner should be displayed instead of a progress bar. The spinner rotates to indicate that the task is in progress.
    .PARAMETER ForegroundColor
        The color of the all text and the completion message. The default value is the current console foreground color.
    .PARAMETER BarForegroundColor
        The color of the progress bar. The default value is the ForegroundColor.
    .PARAMETER BarBackgroundColor
        The background color of the progress bar. The default value is the current console background color.
    .EXAMPLE
        Show-ProgressBar -PercentComplete 50 -Activity "Processing" -Status "Step 1 of 2"
    .EXAMPLE
        Show-ProgressBar -Complete -PercentComplete 100 -Activity "Processing" -Status "Complete"
    .EXAMPLE
        Show-ProgressBar -Spinner -Activity "Processing" -Status "Step 1 of 2"
    .EXAMPLE
        Show-ProgressBar -Spinner -Complete -Activity "Processing" -Status "Complete"
    .NOTES
        If the -Spinner parameter is specified and the -BarChar parameter is set, -BarChar will be ignored..
        The -BarForegroundColor and -BarBackgroundColor parameters cannot be the same.
        PercentComplete must be between 0 and 100.
        BarLength must be at least 1.
        This function DOES NOT support nested progress bars (Nested loops).

    #>

}

function Update-ComputerDNSServers () {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        $ComputerName,
        [Parameter(Mandatory=$true)]
        $DNSServer1,
        [Parameter(Mandatory=$false)]
        $DNSServer2,
        [Parameter(Mandatory=$false)]
        $UseSSL
    )

    if (-not $IsWindows) {
        Write-Host "This function is only available on Windows"
        return
    }
    
    Write-Host "Checking if computer $ComputerName is accessible"
    If (-not (Test-Connection -ComputerName $ComputerName -Quiet)) { 
        Write-Host "Server $ComputerName not accessible"
        return 
    } else {
        Write-Host "Server $ComputerName is accessible via ping"
    }

    If ($UseSSL) {
        $SessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
        $Session = New-PSSession -ComputerName $ComputerName -UseSSL -SessionOption $SessionOptions -ErrorAction SilentlyContinue
    } else {
        $Session = New-PSSession -ComputerName -ErrorAction SilentlyContinue
    }
    If ($Session) {
        Write-Host "Server $ComputerName is accessible via WinRM over HTTPS"
        Invoke-Command -Session $Session -ScriptBlock {
            $NICs = Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true }
            foreach ($NIC in $NICs) {
                $DNSSearchOrder = $NIC.DNSServerSearchOrder
                for ($i=0; $i -lt $DNSSearchOrder.length; $i++) {
                    If ($i -eq 0) {
                        $DNSSearchOrder[$i] = $using:DNSServer1        
                    } elseIf ($i -eq 1) {
                        $DNSSearchOrder[$i] = $using:DNSServer2
                    }
                }
                [void]$NIC.SetDNSServerSearchOrder($DNSSearchOrder)
                [void]$NIC.SetDynamicDNSRegistration("TRUE")
            }
        }
        Remove-PSSession -Session $Session
    } else {
        Write-Host "Server $ComputerName is not accessible via WinRM over HTTPS"
        $Session = New-PSSession -ComputerName $ComputerName -ErrorAction SilentlyContinue
        If ($Session) {
            Write-Host "Server $ComputerName is accessible via WinRM over HTTP"
            Invoke-Command -Session $Session -ScriptBlock {
                $NICs = Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true }
                foreach ($NIC in $NICs) {
                    $DNSSearchOrder = $NIC.DNSServerSearchOrder
                    for ($i=0; $i -lt $DNSSearchOrder.length; $i++) {
                        If ($i -eq 0) {
                            $DNSSearchOrder[$i] = $using:DNSServer1        
                        } elseIf ($i -eq 1) {
                            $DNSSearchOrder[$i] = $using:DNSServer2
                        }
                    }
                    [void]$NIC.SetDNSServerSearchOrder($DNSSearchOrder)
                    [void]$NIC.SetDynamicDNSRegistration("TRUE")
                }
            }
            Remove-PSSession -Session $Session
        } else {
            Write-Host "Server $ComputerName is not accessible via WinRM over HTTPS or HTTP"
        }
    }
    <#
    .SYNOPSIS
        Update the DNS servers for a remote computer.
    .DESCRIPTION
        Update the DNS servers for a remote computer by setting the primary and secondary DNS server IP addresses.
        The target computer must have a WinRM listening service enabled using wither HTTP or HTTPS and be accessible.
    .PARAMETER ComputerName
        The name of the remote computer to update the DNS servers for.
    .PARAMETER DNSServer1
        The IP address of the primary DNS server to set.
    .PARAMETER DNSServer2
        The IP address of the secondary DNS server to set.
    .PARAMETER
        Should the connection use HTTPS
    .EXAMPLE
        Update-ComputerDNSServers -ComputerName "Server01" -DNSServer1 "
    .EXAMPLE
        Update-ComputerDNSServers -ComputerName "Server02" -DNSServer1 -DNSServer2 "
    #>

}

function Debug-String() {
    <#
    .SYNOPSIS
    Outputs a string in diagnostic form or as source code.

    .DESCRIPTION
    With -AsSourceCode: prints a string in single-line form as a double-quoted
    PowerShell string literal that is reusable as source code.

    Otherwise: Prints a string with typically control or hidden characters visualized:

    Common control characters are visualized using PowerShell's own escaping
    notation by default, such as
    "`t" for a tab, "`r" for a CR, but a LF is visualized as itself, as an
    actual newline, unless you specify -SingleLine.

    As an alternative, if you want ASCII-range control characters visualized in caret notation
    (see https://en.wikipedia.org/wiki/Caret_notation), similar to cat -A on Linux,
    use -CaretNotation. E.g., ^M then represents a CR; but note that a LF is
    always represented as "$" followed by an actual newline.

    Any other control characters as well as otherwise hidden characters or
    format / punctuation characters in the non-ASCII range are represented in
    `u{hex-code-point} notation.

    To print space characters as themselves, use -NoSpacesAsDots.

    $null inputs are accepted, but a warning is issued.

    .PARAMETER CaretNotation
    Causes LF to be visualized as "$" and all other ASCII-range control characters
    in caret notation, similar to `cat -A` on Linux.

    .PARAMETER Delimiters
    You may optionally specify delimiters that The visualization of each input string is enclosed in "[...]" to demarcate
    its boundaries. Use -Delimiters '' to suppress that, or specify alternate
    delimiters; you may specify a single string or a 2-element array.

    .PARAMETER NoSpacesAsDots
    By default, space chars. are visualized as "·", the MIDDLE DOT char. (U+00B7)
    Use this switch to represent spaces as themselves.

    .PARAMETER AsSourceCode
    Outputs each input string as a double-quoted PowerShell string
    that is reusable in source code, with embedded double quotes, backticks,
    and "$" signs backtick-escaped.

    Use -SingleLine to get a single-line representation.
    Control characters that have no native PS escape sequence are represented
    using `u{<hex-code-point} notation, which will only work in PowerShell *Core*
    (v6+) source code.

    .PARAMETER SingleLine
    Requests a single-line representation, where LF characters are represented
    as `n instead of actual line breaks.

    .PARAMETER UnicodeEscapes
    Requests that all non-ASCII-range characters - such as foreign letters - in
    the input string be represented as Unicode escape sequences in the form
    `u{hex-code-point}.

    When combined with -AsSourceCode, the result is a PowerShell string literal
    composed of ASCII-range characters only, but note that only PowerShell *Core*
    (v6+) understands such Unicode escapes.

    By default, only control characters that don't have a native PS escape
    sequence / cannot be represented with caret notation are represented this way.

    .EXAMPLE
    PS> "a`ab`t c`0d`r`n" | Debug-String -Delimiters [, ]
    [a`0b`t·c`0d`r`
    ]

    .EXAMPLE
    PS> "a`ab`t c`0d`r`n" | Debug-String -CaretNotation
    a^Gb^I c^@d^M$

    .EXAMPLE
    PS> "a-ü`u{2028}" | Debug-String -UnicodeEscapes # The dash is an em-dash (U+2014)
    a·`u{2014}·`u{fc}

    .EXAMPLE
    PS> "a`ab`t c`0d`r`n" | Debug-String -AsSourceCode -SingleLine # roundtrip
    "a`ab`t c`0d`r`n"
    #>


    [CmdletBinding(DefaultParameterSetName = 'Standard')]
    param(
      [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'Standard', Position = 0)]
      [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'Caret', Position = 0)]
      [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'AsSourceCode', Position = 0)]
      [AllowNull()]
      [object[]] $InputObject,

      [Parameter(ParameterSetName = 'Caret')]
      [switch] $CaretNotation,

      [Parameter(ParameterSetName = 'Standard')]
      [Parameter(ParameterSetName = 'Caret')]
      [string[]] $Delimiters,

      [Parameter(ParameterSetName = 'Standard')]
      [switch] $NoSpacesAsDots,

      [Parameter(ParameterSetName = 'AsSourceCode')]
      [switch] $AsSourceCode,

      [Parameter(ParameterSetName = 'Standard')]
      [Parameter(ParameterSetName = 'AsSourceCode')]
      [switch] $SingleLine,

      [Parameter(ParameterSetName = 'Standard')]
      [Parameter(ParameterSetName = 'Caret')]
      [Parameter(ParameterSetName = 'AsSourceCode')]
      [switch] $UnicodeEscapes

    )

    begin {
      if ($UnicodeEscapes) {
        $re = [regex] '(?s).' # *all* characters.
      } else {
        # Only control / separator / punctuation chars.
        # * \p{C} matches any Unicode control / format/ invisible characters, both inside and outside
        # the ASCII range; note that tabs (`t) are control character too, but not spaces; it comprises
        # the following Unicode categories: Control, Format, Private_Use, Surrogate, Unassigned
        # * \p{P} comprises punctuation characters.
        # * \p{Z} comprises separator chars., including spaces, but not other ASCII whitespace, which is in the Control category.
        # Note: For -AsSourceCode we include ` (backticks) too.
        $re = if ($AsSourceCode) { [regex] '[`\p{C}\p{P}\p{Z}]' } else { [regex] '[\p{C}\p{P}\p{Z}]' }
      }
      $openingDelim = $closingDelim = ''
      if ($Delimiters) {
        $openingDelim = $Delimiters[0]
        $closingDelim = $Delimiters[1]
        if (-not $closingDelim) { $closingDelim = $openingDelim }
      }
    }

    process {
      if ($null -eq $InputObject) { Write-Warning 'Ignoring $null input.'; return }
        foreach ($str in $InputObject) {
            if ($null -eq $str) { Write-Warning 'Ignoring $null input.'; continue }
            if ($str -isnot [string]) { $str = -join ($str | Out-String -Stream) }
            $strViz = $re.Replace($str, {
                param($match)
                $char = [char] $match.Value[0]
                $codePoint = [uint16] $char
                $sbToUnicodeEscape = { '`u{' + '{0:x}' -f [int] $Args[0] + '}' }
                # wv -v ('in [{0}]' -f [char] $match.Value)
                if ($CaretNotation) {
                    if ($codePoint -eq 10) {
                        # LF -> $<newline>
                        '$' + $char
                    } elseif ($codePoint -eq 32) {
                        # space char.
                        if ($NoSpacesAsDots) { ' ' } else { '·' }
                    } elseif ($codePoint -ge 0 -and $codePoint -le 31 -or $codePoint -eq 127) {
                        # If it's a control character in the ASCII range,
                        # use caret notation too (C0 range).
                        # See https://en.wikipedia.org/wiki/Caret_notation
                        '^' + [char] (64 + $codePoint)
                    }
                    elseif ($codePoint -ge 128) {
                    # Non-ASCII (control) character -> `u{<hex-code-point>}
                        & $sbToUnicodeEscape $codePoint
                    } else {
                        $char
                    }
                } else {
                    # -not $CaretNotation
                    # Translate control chars. that have native PS escape sequences
                    # into these escape sequences.
                    switch ($codePoint) {
                        0  { '`0'; break }
                        7  { '`a'; break }
                        8  { '`b'; break }
                        9  { '`t'; break }
                        11 { '`v'; break }
                        12 { '`f'; break }
                        10 { if ($SingleLine) { '`n' } else { "`n" }; break }
                        13 { '`r'; break }
                        27 { '`e'; break }
                        32 { if ($AsSourceCode -or $NoSpacesAsDots) { ' ' } else { '·' }; break } # Spaces are visualized as middle dots by default.
                        default {
                            if ($codePoint -ge 128) {
                                & $sbToUnicodeEscape $codePoint
                            } elseif ($AsSourceCode -and $codePoint -eq 96) { # ` (backtick)
                                '``'
                            } else {
                                $char
                            }
                        }
                    } # switch
                }
                }) # .Replace

                # Output
            if ($AsSourceCode) {
                '"{0}"' -f ($strViz -replace '"', '`"' -replace '\$', '`$')
            }
            else {
                if ($CaretNotation) {
                    # If a string *ended* in a newline, our visualization now has
                    # a trailing LF, which we remove.
                    $strViz = $strViz -replace '(?s)^(.*\$)\n$', '$1'
                }
                $openingDelim + $strViz + $closingDelim
            }
        }
    } # process
}

function Get-FolderStats() {
    Param(
        [Parameter(Mandatory)]
        [string]$Path,
        [switch]$Recurse,
        [switch]$IncludeTypeStats,
        [switch]$ShowProgress,
        [ValidateSet("Standard","Text")]
        [string]$ProgressType = "Standard"
    )

    $params = @{}
    if ($Recurse.IsPresent) {
        $params.Add("Recurse", $true)
    } else {
        $params.Add("Recurse", $false)
    }
    Write-Host "Gathering files... This may take a while if you selected recuse on a large folder!"
    $Items = Get-ChildItem -Path $Path @params -File
    $Stats = $Items | Measure-Object -Property Length -Sum | Select-Object Count, @{Name="Size"; Expression={$_.Sum / 1mb}}
    $Stats | Add-Member -MemberType NoteProperty -Name "Folder" -Value $Path

    $threeYrStats = $Items.Where({$_.LastWriteTime -lt (Get-Date).AddYears(-3)}) | Measure-Object -Property Length -Sum | Select-Object Count, @{Name="Size";Expression={$_.Sum / 1mb}}
    $fourYrStats = $Items.Where({$_.LastWriteTime -lt (Get-Date).AddYears(-4)}) | Measure-Object -Property Length -Sum | Select-Object Count, @{Name="Size";Expression={$_.Sum / 1mb}}
    $fiveYrStats = $Items.Where({$_.LastWriteTime -lt (Get-Date).AddYears(-5)}) | Measure-Object -Property Length -Sum | Select-Object Count, @{Name="Size";Expression={$_.Sum / 1mb}}

    [PSCustomObject]@{
        ThreeYearFiles = $threeYrStats.count
        ThreeYearSize = $threeYrStats.Size
        FourYearFiles = $fourYrStats.Count
        FourYearSize = $fourYrStats.Size
        FiveYearFiles = $fiveYrStats.Count
        FiveYearSize = $fiveYrStats.Size
    }

    # Stats per file type

    if ($IncludeTypeStats.IsPresent) {
        Write-Host "Gathering Type statistics... This may take a while if you selected recuse on a large folder!"
        $TypeStats = [List[PsObject]]::New()

        $Types = $Items | Group-Object -Property Extension | Where-Object {$_.Name -ne ''}

        foreach ($Type in $Types) {

            If ($ShowProgress) {
                $INdex = $Types.IndexOf($Type)
                $PercentComplete = [math]::Round(($Index / $Types.Count) * 100)
                if ($ProgressType -eq "Standard") {
                    Write-Progress -PercentComplete $PercentComplete -Activity "Gathering Type statistics" -Status "Processing $Type"
                } else {
                    Show-ProgressBar -Activity "Gathering Type statistics" -Status "Processing $Type" -PercentComplete $PercentComplete
                }
            }

            $TypeSize = ($Type.Group | Measure-Object -Property Length -Sum).Sum / 1mb

            if ($TypeSize -ge 0.01) {
                $TypeStat = [PSCustomObject]@{
                    Name = ( ('' -eq $Type.Name) ? "Undefined" : $Type.Name )
                    Count = $Type.Count
                    Size = $TypeSize
                }
                $TypeStats.Add($TypeStat)
            }
        }
        if ($ProgressType -eq "Standard") {
            Write-Progress -Completed
        } else {
            Show-ProgressBar -Completed
        }

        if ($TypeStats.Count -gt 0) {
            $Stats | Add-Member -MemberType NoteProperty -Name "TypeStats" -Value ($TypeStats.toArray())
        }
    }
    return $Stats
    <#
    .SYNOPSIS
        Get statistics for a folder.
    .DESCRIPTION
        Get statistics for a folder, including the number of files, total size, and optionally statistics per file type.
    .PARAMETER Path
        The path to the folder for which to get statistics.
    .PARAMETER Recurse
        If present, statistics are gathered recursively, including all subfolders.
    .PARAMETER IncludeTypeStats
        If present, statistics are gathered per file type.
    .PARAMETER ShowProgress
        If present, a progress bar is displayed while gathering statistics.
    .PARAMETER ProgressType
        The type of progress bar to display. Options are "Standard" or "Text".
    .EXAMPLE
        Get-FolderStats -Path "C:\Temp" -Recurse -IncludeTypeStats
        Get statistics for the folder "C:\Temp", including subfolders, and per file type.
    .EXAMPLE
        Get-FolderStats -Path "C:\Temp" -ShowProgress
        Get statistics for the folder "C:\Temp" and display a progress bar while gathering statistics.
    #>

}

function Get-FunctionNamesInFiles () {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$Path,
        [string[]]$Exclude,
        [string]$Filter,
        [switch]$Recurse

    )

    try {
        $params = @{
            Path = $Path
        }
        if ($Exclude) {
            $params.Add("Exclude", $Exclude)
        }
        if ($Filter) {
            $params.add("Filter", $Filter)
        }
        if ($Recurse) {
            $params.Add("Recurse", $true)
        }

        Get-ChildItem @params | ForEach-Object {
            $Command = Get-Command $_
            $Command.ScriptBlock.Ast.FindAll({$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]}, $false).Name
        } | ForEach-Object {
            "'$_',"
        }
    } catch {
        throw $_
    }
    <#
    .SYNOPSIS
        Get the names of functions defined in files.
    .DESCRIPTION
        Get the names of functions defined in files in a specified folder.
    .PARAMETER Path
        The path to the folder containing the files to search.
    .PARAMETER Exclude
        An array of file names or patterns to exclude from the search.
    .PARAMETER Filter
        A wildcard pattern to filter the files to search.
    .PARAMETER Recurse
        If present, the search is performed recursively, including all subfolders.
    .EXAMPLE
        Get-FunctionNamesInFiles -Path "C:\Scripts"
        Get the names of functions defined in files in the "C:\Scripts" folder.
    .EXAMPLE
        Get-FunctionNamesInFiles -Path "C:\Scripts" -Recurse
        Get the names of functions defined in files in the "C:\Scripts" folder and all subfolders.
    .EXAMPLE
        Get-FunctionNamesInFiles -Path "C:\Scripts" -Exclude "*.ps1"
        Get the names of functions defined in files in the "C:\Scripts" folder, excluding all .ps1 files.
    .EXAMPLE
        Get-FunctionNamesInFiles -Path "C:\Scripts" -Filter "*.psm1"
        Get the names of functions defined in files in the "C:\Scripts" folder, including only .psm1 files.
    .EXAMPLE
        Get-FunctionNamesInFiles -Path "C:\Scripts" -Recurse -Exclude "*.ps1" -Filter "*.psm1"
        Get the names of functions defined in files in the "C:\Scripts" folder and all subfolders, excluding .ps1 files and including only .psm1 files.
    #>

}

function Get-ProcessStatus () {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$ProcessName,
        [switch]$CPU,
        [switch]$Memory
    )

    $NumberOfLogicalProcessors=(Get-WmiObject -class Win32_processor | Measure-Object -Sum NumberOfLogicalProcessors).Sum -1

    if ($CPU) {
        $Counter = "\Process({0})\% Processor Time" -f $ProcessName
        $cookedValue = ((Get-Counter $Counter).Countersamples).cookedValue
        $value = [math]::Round(($cookedValue) / $NumberOfLogicalProcessors , 1)
        write-host "$Process CPU Utilization: $value"
    }

    If ($Memory) {
        $Counter = "Process({0})\Working Set" -f $ProcessName
        $cookedValue = ((Get-Process $Counter).Countersamples).cookedValue
        $Value = [math]::Round(($CookedValue)/1023/1024 ,1)
        Write-Host "Process memory utilization $value"
    }
    <#
    .SYNOPSIS
        Get the CPU and memory utilization of a process.
    .DESCRIPTION
        Get the CPU and memory utilization of a process by specifying the process name.
    .PARAMETER ProcessName
        The name of the process for which to get the CPU and memory utilization.
    .PARAMETER CPU
        If present, the CPU utilization of the process is returned.
    .PARAMETER Memory
        If present, the memory utilization of the process is returned.
    .EXAMPLE
        Get-ProcessStatus -ProcessName "notepad" -CPU
        Get the CPU utilization of the "notepad" process.
    .EXAMPLE
        Get-ProcessStatus -ProcessName "notepad" -Memory
        Get the memory utilization of the "notepad" process.
    .EXAMPLE
        Get-ProcessStatus -ProcessName "notepad" -CPU -Memory
        Get the CPU and memory utilization of the "notepad" process.
    #>

}

function Update-DataBaseMailCredentials {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$SqlServer,
        [Parameter(Mandatory)]
        [Parameter(Mandatory=$false)]
        [string]$MailAccount,
        [Parameter(Mandatory)]
        [string]$MailUser,
        [Parameter(Mandatory)]        
        [string]$MailPassword
    )

    # This SQL script retrieves the current SQL Database Mail configurations
    $sqlGetDbMailAccounts = @"
SELECT [sysmail_server]
    ,[account_id]
    ,[sysmail_account].[name] AS [AccountName]
    ,[servertype]
    ,[servername] AS [SMTPServerAddress]
    ,[Port]
    ,[Username]

FROM [msdb].[dbo].[sysmail_server]
INNER JOIN [msdb].[dbo].[sysmail_account]
ON [sysmail_server].[account_id]=[sysmail_account].[account_id]
"@


If ($MailAccount) {
    $sqlGetDbMailAccounts += " WHERE [sysmail_account].[name] = '$MailAccount'"
}

    # this SQL script updates the Database Mail account for the Account Id.
    $sqlUpdateDbMailAccount = @"
EXEC [dbo].[sysmail_update_account_sp]
    @account_id='{0}'
    ,@username='{1}'
    ,@password='{2}'
"@


    # Retrieve the Database Mail Accounts
    $dbMailAccounts = Invoke-Sqlcmd -ServerInstance $sqlserver -Database msdb -Query $sqlGetDbMailAccounts

    # Loop through each account and check if the username has changed.
    # if the username has changed update the Database Mail configuration.
    foreach ($dbMailAccount in $dbMailAccounts) {
        If ($dbMailAccount.Username -ne $ses_creds.SmtpUsername) {
            try{
                $Procedure = $sqlUpdateDbMailAccount -f $dbMailAccount.account_Id, $MailUser, $MailPassword
                $result = Invoke-Sqlcmd -ServerInstance $sqlserver -Database 'msdb' -Query $Procedure
            } catch {
                Write-Log $result
                throw $result
            }
            $msg = "Database mail account {0} updated to new credentials." -f $dbMailAccount.AccountName
            Write-Log $msg
        }
    }
    <#
    .SYNOPSIS
        Update the credentials for SQL Database Mail.
    .DESCRIPTION
        Update the credentials for SQL Database Mail by specifying the SQL Server, Database, Mail Server, Mail User, and Mail Password.
    .PARAMETER SqlServer
        The SQL Server to update the Database Mail credentials.
    .PARAMETER MailAccount
        The name of the Database Mail account to update. If not specified, all accounts are updated.
    .PARAMETER MailUser
        The new username for the Database Mail account.
    .PARAMETER MailPassword
        The new password for the Database Mail account.
    .EXAMPLE
        Update-DataBaseMailCredentials -SqlServer "SQLServer01" -MailAccount "MailAccount01" -MailUser "user01" -MailPassword "password01"
        Update the credentials for the Database Mail account "MailAccount01" on SQL Server "SQLServer01" with the username "user01" and password "password01".
    .EXAMPLE
        Update-DataBaseMailCredentials -SqlServer "SQLServer01" -MailUser "user01" -MailPassword "password01"
        Update the credentials for all Database Mail accounts on SQL Server "SQLServer01" with the username "user01" and password "password01".
    #>

}

function ConvertTo-LocalTime() {
    param( 
        [parameter(Mandatory=$true)]
        [Datetime]$DateTime 
    )
    $tz = Get-TimeZone
    $result = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($DateTime, $tz.StandardName)
    return $result
    <#
    .SYNOPSIS
        Convert a time to local time.
    .DESCRIPTION
        Convert a time to local time.
    .PARAMETER DateTime
        The time to convert to local time.
    .EXAMPLE
        ConvertTo-LocalTime -DateTime "2021-01-01 12:00:00"
        Convert the UTC time "2021-01-01 12:00:00" to local time.
    .EXAMPLE
        ConvertTo-LocalTime -DateTime (Get-Date)
        Convert the current UTC time to local time.
    #>

}

function ConvertFrom-LocalTime() {
    [CmdletBinding(DefaultParameterSetName = 'Name')]
    Param(
        [Parameter(
            Mandatory = $true
        )]
        [DateTime]$DateTime,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = "Name"
        )]
        [String]$StandardName,
        [Parameter(
            Mandatory =$true,
            ParameterSetName = "tz"
        )]
        [TimeZoneInfo]$Tz
    )
    if ($StandardName) {
        $Tz = Get-TimeZone -Name $StandardName
    }

    $result = [System.TimeZoneInfo]::ConvertTime($datetime, $Tz)
    return $result
    <#
    .SYNOPSIS
        Convert a local time to UTC time.
    .DESCRIPTION
        Convert a local time to UTC time.
    .PARAMETER DateTime
        The local time to convert to UTC time.
    .PARAMETER StandardName
        The standard name of the time zone to use for the conversion.
    .PARAMETER Tz
        The time zone to use for the conversion.
    .EXAMPLE
        ConvertFrom-LocalTime -DateTime "2021-01-01 12:00:00" -StandardName "Eastern Standard Time"
        Convert the local time "2021-01-01 12:00:00" to UTC time using the "Eastern Standard Time" time zone.
    .EXAMPLE
        ConvertFrom-LocalTime -DateTime (Get-Date) -Tz (Get-TimeZone -Name "Eastern Standard Time")
        Convert the current local time to UTC time using the "Eastern Standard Time" time zone.
    #>

}

function ConvertFrom-UTC() {
    Param(
        [Parameter(
            Mandatory = $true
        )]
        [datetime]$DateTime
    )
    $tz = Get-TimeZone
    $result = [System.TimeZoneInfo]::ConvertTimeFromUtc($Datetime, $tz)
    return $result
    <#
    .SYNOPSIS
        Convert a UTC time to local time.
    .DESCRIPTION
        Convert a UTC time to local time.
    .PARAMETER DateTime
        The UTC time to convert to local time.
    .EXAMPLE
        ConvertFrom-UTC -DateTime "2021-01-01 12:00:00"
        Convert the UTC time "2021-01-01 12:00:00" to local time.
    .EXAMPLE
        ConvertFrom-UTC -DateTime (Get-Date)
        Convert the current UTC time to local time.
    #>

}

function ConvertTo-UTC() {
    Param(
        [Parameter(
            Mandatory = $true
        )]
        [DateTime]$time
    )
    $tz = Get-TimeZone
    $result = [System.TimeZoneInfo]::ConvertTimeToUtc($datetime, $tz)
    return $result

    <#
    .SYNOPSIS
        Convert a local time to UTC time.
    .DESCRIPTION
        Convert a local time to UTC time.
    .PARAMETER DateTime
        The local time to convert to UTC time.
    .EXAMPLE
        ConvertTo-UTC -DateTime "2021-01-01 12:00:00"
        Convert the local time "2021-01-01 12:00:00" to UTC time.
    .EXAMPLE
        ConvertTo-UTC -DateTime (Get-Date)
        Convert the current local time to UTC time.
    #>

}

function Write-ConsoleOnly {
    [CmdletBinding()]
    param(
        [Parameter(Position=0, ValueFromPipeline=$true)]
        [string]$Message='',
        
        [Parameter()]
        [ConsoleColor]$ForegroundColor = [Console]::ForegroundColor,
        
        [Parameter()]
        [ConsoleColor]$BackgroundColor = [Console]::BackgroundColor,
        
        [Parameter()]
        [switch]$NoNewline
    )
    
    # Save original colors
    $originalForeground = [Console]::ForegroundColor
    $originalBackground = [Console]::BackgroundColor
    
    # Set new colors
    [Console]::ForegroundColor = $ForegroundColor
    [Console]::BackgroundColor = $BackgroundColor
    
    # Write to console only
    if ($NoNewline) {
        [Console]::Write($Message)
    } else {
        [Console]::WriteLine($Message)
    }
    
    # Restore original colors
    [Console]::ForegroundColor = $originalForeground
    [Console]::BackgroundColor = $originalBackground

    <#
    .SYNOPSIS
        Write a message to the console only.
    .DESCRIPTION
        Write a message to the console only, without sending it to the pipeline.
    .PARAMETER Message
        The message to write to the console.
    .PARAMETER ForegroundColor
        The foreground color of the message.
    .PARAMETER BackgroundColor
        The background color of the message.
    .PARAMETER NoNewline
        If present, the message is written without a newline character.
    .Example
        Write-ConsoleOnly -Message "This is a test message"
        Write the message "This is a test message" to the console.
    .EXAMPLE
        Write-ConsoleOnly -Message "This is a test message" -ForegroundColor Green -BackgroundColor Black
        Write the message "This is a test message" to the console with green foreground and black background colors.
    .EXAMPLE
        Write-ConsoleOnly -Message "This is a test message" -ForegroundColor Green -BackgroundColor Black -NoNewline
        Write the message "This is a test message" to the console with green foreground and black background colors without a newline character.
    .EXAMPLE
        "This is a test message" | Write-ConsoleOnly -ForegroundColor Green -BackgroundColor Black
        Write the message "This is a test message" to the console with green foreground and black background colors.
    .EXAMPLE
        "This is a test message" | Write-ConsoleOnly -ForegroundColor Green -BackgroundColor Black -NoNewline
        Write the message "This is a test message" to the console with green foreground and black background colors without a newline character.
    #>

}