RyanTools.psm1

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
#
# Copyright 2019 Ryan James Decker

function Compare-FileHash {
    <#

        .SYNOPSIS
        Compare two files' hashes.

        .DESCRIPTION
        Pick two files and an encryption algorithm to check their hashes to confirm integrity.

    #>


    Param(
        [ValidateSet(
            'SHA1',
            'SHA256',
            'SHA384',
            'SHA512',
            'MACTripleDES',
            'MD5',
            'RIPEMD160'
        )][string]$Algorithm,

        [string]$FirstFile,

        [string]$SecondFile
    )

    Begin{}

    Process {

        $_nl = [System.Environment]::NewLine

        $_firstHash = 
            Get-FileHash -Path $FirstFile -Algorithm $Algorithm
        
        $_secondHash = 
            Get-FileHash -Path $SecondFile -Algorithm $Algorithm
        
        $_output = @(
            @{
                $FirstFile=$_firstHash.hash
            },
            @{
                $SecondFile=$_secondHash.hash
            }
        )
    
        if (
            $_firstHash.hash -eq $null -or 
            $_secondHash.hash -eq $null
        ) {
            Write-Host "Something went wrong."
            return
        }

        $_output

        if (
            $_firstHash.hash -eq $_secondHash.hash
        ) {
            return $true
        } else {
            return $false
        }

        End{}
    }
}

function Get-DesktopMonitorInfo {
    
    <#
        .SYNOPSIS
        Get desktop monitor information like resolution, etc...

        .DESCRIPTION
        This is a wrapper for querying WMI for the Win32_DesktopMonitor class.

        .PARAMETER ComputerName
        Enter an array of hosts to query the desktop monitor statistics.

        .PARAMETER Credential
        This will invoke Get-Credential and use your credentials with Get-WmiObject Win32_DesktopMonitor.

        .INPUTS
        None
        
        .OUTPUTS
        None
    #>


    Param(
        $ComputerName,
        $Credential    
    )

    $_gwmiArgs = @{Class = 'Win32_DesktopMonitor'}
    if ($ComputerName) {$_gwmiArgs.Add('ComputerName',$ComputerName)}
    if ($Credential) {$_gwmiArgs.Add('Credential',(Get-Credential $Credential))}

    Get-WmiObject @_gwmiArgs | Select-Object `
        SystemName,
        DeviceId,
        Name,
        PixelsPerXLogicalInch,
        PixelsPerYLogicalInch,
        ScreenHeight,
        ScreenWidth,
        Status

}

function Get-IpV4NetworkId {
    <#
        .SYNOPSIS
        Calculates the network address from ipv4 and netmask.

        .DESCRIPTION
        Enter an IP address and a subnet mask to find out the ip address of the network. If you don't enter a subnet mask, the 24 bit mask is used by default.

        .PARAMETER IpV4Address
        Alias: ip,ipaddress,ipv4
        Enter an IP address to find out what network it is on.

        .PARAMETER SubnetMask
        Alias: mask,subnet,snm
        Enter a subnet mask to calculate the network address from the ip address.

        .EXAMPLE
        Get-IpV4NetworkId -IpV4Address 192.168.190.253 -SubnetMask 255.255.240.0

        192.168.176.0
    
        In the example above, if you enter 192.168.190.253 with a mask of 255.255.240.0, the network ID is 192.168.176.0.

        .INPUTS
        Piping not supported.

        .OUTPUTS
        String

    #>


    Param (

        [Parameter(Mandatory=$true)]
        [Alias("ip","ipaddress","ipv4")]
        $IpV4Address = "10.20.30.190",

        [Alias("mask","subnet","snm")]
        $SubnetMask = "255.255.255.192"

    )

    Begin {

        if (

            $IpV4Address -notmatch "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" -or 

            $SubnetMask  -notmatch "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"

        ) {

            Write-Error "Ip address or subnet mask not formatted correctly."

        }

        [uint16[]]$_ipAddressOctets =  $IpV4Address -split '\.'

        [uint16[]]$_subnetMaskOctets = $SubnetMask  -split '\.'

        [uint16[]]$_ipAddressOctets | % {

            if ($_ -lt 0 -or $_ -gt 255) {
                Write-Output $_
                Write-Error "Ip address octets must be from 0 to 255, inclusive."

            }
        }

        [uint16[]]$_subnetMaskOctets | % {
        
            if ($_ -lt 0 -or $_ -gt 255) {
                Write-Output $_
                Write-Error "Subnet mask octets must be from 0 to 255, inclusive."

            }
        }
    }

    Process {

        $_networkIdOctets = New-Object -TypeName uint16[] 4

        for ($i = 0; $i -lt 4; $i++) {

            $_networkIdOctets[$i] = $_ipAddressOctets[$i] -band $_subnetMaskOctets[$i]

        }
    
        $_networkId = $_networkIdOctets -join "."

    
    }

    End {return $_networkId}

}

function Get-LogonHistory {
    <#
        .SYNOPSIS
        Shows user logon and logoff times.

        .DESCRIPTION
        This command searches Windows System logs for events 7001 and 7002 and returns

        .INPUTS
        None

        .OUTPUTS
        psobject[]
    #>


    Param(

        [int64]$MaxEvents,
        $ComputerName,
        $Credential

    )

    Begin {
        
        $_icmArgs = @{}
        
        if ($MaxEvents) {
            $_icmArgs += @{
                ArgumentList=@(
                    $MaxEvents
                )
            }
        }

        if ($ComputerName) {$_icmArgs += @{ComputerName=$ComputerName}}

        if ($Credential) {$_icmArgs += @{Credential=$Credential}}

    }

    Process {
        
        $_scriptblock = {
            
            Param (

                $MaxEvents
            
            )

            $_getWinEventArgs = @{}

            if ($MaxEvents) { $_getWinEventArgs += @{MaxEvents=$MaxEvents} }

            $_xpath = @"

                *[System[EventID=7001 or EventID=7002]]

"@

            
            $_getWinEventArgs = @{}

            if ($MaxEvents) {$_getWinEventArgs = @{MaxEvents=$MaxEvents}}

            $_events = 
                Get-WinEvent `
                    -LogName system `
                    -FilterXPath $_xpath `
                    @_getWinEventArgs

            $_results = @()

                                                                                                                                                    $_events | % {

            [xml[]]$_eventXml = $_.ToXml()

            $_sidRaw = $_eventXml.Event.EventData.Data | ? {
                $_.Name -eq 'UserSid'
            }

            $_sid = $_sidRaw.'#text'
        
            $_user = 
                Get-WmiObject `
                    -Class win32_useraccount `
                    -Filter "SID='$_sid'"
        
            $_results += 
                New-Object `
                    -TypeName psobject `
                    -Property @{
                    
                        Event = switch ($_.id) {

                            7001 {
                                "Logon"
                                break
                            }

                            7002 {
                                "Logoff"
                                break
                            }

                            default {
                                break
                            }

                        }

                        DateTime = Get-Date $_.TimeCreated

                        User = $_user.Caption
                    
                    }
            }
        
            return $_results
        }

        $_icmArgs += @{ScriptBlock=$_scriptblock}

        Invoke-Command @_icmArgs
    }

    End {
        
    }

}

function Get-NetworkInterfaceConfiguration {

    Param(
        $ComputerName,
        $Credential
    )
    
    Begin {

        $_gwmiArgs = @{
            
            Class = "Win32_NetworkAdapterConfiguration"

            Filter = "IpEnabled=TRUE AND MacAddress IS NOT NULL"
                
        }

        if ($ComputerName) {$_gwmiArgs.Add("ComputerName",$ComputerName)}

        if ($Credential) {$_gwmiArgs.Add("Credential",$Credential)}

        
    }

    Process{

        Get-WmiObject @_gwmiArgs | Select-Object `
            DnsHostName,

            DnsDomain,
            
            @{
                Name="Adapter Name";
                Expression={$_.Caption}
            },
            
            @{
                Name="IP Address";
                Expression={$_.IpAddress}
            },
            
            @{
                Name="Subnet Mask";
                Expression={$_.IpSubnet}
            },
            
            @{
                Name="MAC Address";
                Expression={$_.MacAddress}
            },
            
            @{
                Name="Default Gateway";
                Expression={$_.DefaultIpGateway}
            },
            
            @{
                Name="DNS Servers";
                Expression={$_.DnsServerSearchOrder}
            },
            
            @{
                Name="DHCP Server";
                Expression={$_.DhcpServer}
            },
            
            @{
                Name="DHCP Lease Timestamp";
                Expression={
                    $_year   = $_.DhcpLeaseObtained.Substring(0,4)
                    $_month  = $_.DhcpLeaseObtained.Substring(4,2)
                    $_day    = $_.DhcpLeaseObtained.Substring(6,2)
                    $_hour   = $_.DhcpLeaseObtained.Substring(8,2)
                    $_minute = $_.DhcpLeaseObtained.Substring(10,2)
                    $_second = $_.DhcpLeaseObtained.Substring(12,2)
                    Get-Date "$_year-$_month-$_day $_hour`:$_minute`:$_second"
                }
            },
            
            @{
                Name="DHCP Lease Expiration";
                Expression={
                    $_year   = $_.DhcpLeaseExpires.Substring(0,4)
                    $_month  = $_.DhcpLeaseExpires.Substring(4,2)
                    $_day    = $_.DhcpLeaseExpires.Substring(6,2)
                    $_hour   = $_.DhcpLeaseExpires.Substring(8,2)
                    $_minute = $_.DhcpLeaseExpires.Substring(10,2)
                    $_second = $_.DhcpLeaseExpires.Substring(12,2)
                    Get-Date "$_year-$_month-$_day $_hour`:$_minute`:$_second"
                }
            }
    }

    End{}
}

function Get-TrustedHosts {
    <#
        
        .SYNOPSIS
        Simple command that gets the trusted hosts configuration in WsMan

        .DESCRIPTION


        .INPUTS
        None

        .OUTPUTS
        [Microsoft.WSMan.Management.WSManConfigElement] or [System.Array]
    #>


    [CmdletBinding(
        
        DefaultParameterSetName = 'Object'

    )]

    Param(

        [Parameter(
            ParameterSetName = 'Object'
        )]
        [switch]$AsObject = $true,

        [Parameter(
            ParameterSetName = 'Array'
        )]
        [switch]$AsArray = $false

    )

    Begin{

        $_trustedHosts = Get-Item WSMan:\localhost\Client\TrustedHosts

        $_trustedHostsAsArray = $_trustedHosts.Value -split ','

    }

    Process{
        
        if ($AsArray.IsPresent) { $AsObject = $AsObject:false }

    }

    End{
        
        if ($AsObject.IsPresent) {$_trustedHosts}

        if ($AsArray.IsPresent) {$_trustedHostsAsArray}
        
    }

}

function Get-WlanInfo {

    $_netshOutputArray = NETSH WLAN SHOW INTERFACES

    $_adapterOutputStrings = 
        $_netshOutputArray.trim() | 
            ? {
                $_ -match " : "
            }

    $_adapterProperties = @{}

    foreach (

        $_adapterOutputString in $_adapterOutputStrings

    ) {

        $_adapterPropertyArray = 
            $_adapterOutputString -split " : "
    
        $_adapterPropertyName = $_adapterPropertyArray[0].Trim()
        $_adapterPropertyValue = $_adapterPropertyArray[1].Trim()

        $_adapterProperties["$_adapterPropertyName"] = 
            "$_adapterPropertyValue"

    }

    $_adapterProperties.GetEnumerator() | 
        Sort-Object Name

}

function Invoke-AsynchronousPing {
    <#

        .SYNOPSIS
        Ping many hosts asynchronously.

        .DESCRIPTION
        The parallel pinging of many hosts is asychronous but the script's entire
        execution is not. This script is blocking only until the longest ping
        finishes.

        Each host is sent four ICMP echo requests. The output comes out in three
        sections:

        1) Hosts that responded
        2) Hosts that did not respond
        3) A full summary of all the ping activity.

        I recommend running this with the -Verbose switch if you'd like to get all
        of the information during execution.

        .PARAMETER ComputerName
        Aliases: cn hostname host

        ComputerName accepts an array of the computers you're wanting to ping in
        parallel.

    #>


    Param(

        [Alias('cn','hostname','host')]
        [Parameter(
            Mandatory=$true,
            ValueFromPipeline=$true
        )]
        [ValidateNotNullOrEmpty()]
        $ComputerName

    )

    Begin {

        Get-Variable | Out-String | Write-Verbose
        $_nl = [System.Environment]::NewLine

    }

    Process {
    
        Write-Host "Starting computer connections...$_nl"

        foreach($_computer in $ComputerName) {
        
            Write-Verbose "Starting ping for $_computer..."
            Test-Connection -ComputerName $_computer -AsJob | Out-Null

        }

        while (

            (gjb | ? {$_.JobStateInfo -notmatch 'complete'}).length -ne 0

        ) {
        
            $_unprocessed = (gjb | ? {$_.JobStateInfo -notmatch 'complete'}).length
            $_total = (gjb).length
            $_processed = $_total - $_unprocessed
            $_progress = $_processed / $_total * 100

            Write-Progress `
                -Activity "Waiting for computers to finihs pinging..." `
                -Status (
                    "Waiting for " + 
                    (gjb | ? {
                            $_.JobStateInfo -notmatch 'complete'
                    }).length + 
                    " computers to fininsh..."
                ) `
                -PercentComplete $_progress

            sleep -Seconds 1

        }

        Write-Verbose "All pings finished."
    
        Write-Host "These computers responded to at least one ping:"
        gjb | 
            rcjb -k | ? {

                $_.ReplySize -ne $null

            } | group Address | % {

                    $_.Group | 
                        select Address,ReplySize,ResponseTime `
                            -First 1

                } | ft -au
    
        Write-Host "These computers failed at least one ping response:"
        gjb | 
            rcjb -k | ? {

                $_.ReplySize -eq $null

            } | group Address | % {

                    $_.Group | 
                        select Address,ReplySize,ResponseTime `
                            -First 1

                } | ft -au


        Write-Host "All results:"
        gjb | rcjb | select Address,ReplySize,ResponseTime | ft -au

        gjb | rjb

    }

    End { }
}

function New-Shortcut {
    <#
        .SYNOPSIS
        Easily create a shortcut with PowerShell.

        .DESCRIPTION
        This is a wrapper for the Windows Script Host WshShortcut Object

        .PARAMETER ShortcutPath
        Alias: Path
        This is the filename for your shortcut file.

        .PARAMETER TargetPath
        Alias: Target
        This is what your shortcut will reference.

        .PARAMETER Arguments
        Specify which arguments will be sent to the target.

        .PARAMETER WorkingDirectory
        Set a directory for the context of the target's execution.

        .PARAMETER Description
        You can include a description with this parameter.

        .PARAMETER FullName
        Alias: FullPath
        This is the full path to your shortcut file.
    
        .PARAMETER HotKey
        Alias: KeyboardShortcut
        Enter a string as a keyboard shortcut to fire off your shortcut.
    
        .PARAMETER IconLocationPath
        Alias: IconPath
        Enter a path to the file that has your icon graphic.

        .PARAMETER IconLocationIndex
        Alias: IconIndex
        If your icon file path has multiple icons, you can select the index
        starting at 0. Usually exe or dll files will have multiple icon indicies.

        .PARAMETER RelativePath
        This is the relative path to your shortcut file.

        .PARAMETER WindowStyle
        This sets the way the window of your target will be opened. The three
        choices are Default, Minimized, and Maximized.

        .LINK
        https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/windows-scripting/9bbdkx3k(v%3dvs.84)

        .LINK
        https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/windows-scripting/xk6kst2k%28v%3dvs.84%29
    #>


    Param(
        [Parameter(
            Mandatory=$true
        )]
        [Alias('Path')]
        $ShortcutPath,

        [Parameter(
            Mandatory=$true
        )]
        [Alias('Target')]
        $TargetPath,

        $Arguments,

        $WorkingDirectory = '',

        $Description,
    
        [Alias('FullPath')]
        $FullName,
    
        [Alias('KeyboardShortcut')]
        $HotKey,

        [Alias('IconPath')]
        $IconLocationPath,
    
        [Alias('IconIndex')]
        [uint16]
        $IconLocationIndex = 0,

        $RelativePath,

        [ValidateSet(
            'Default',
            'Maximized',
            'Minimized'
        )]
        $WindowStyle
    )

    Begin {
    }

    Process {

        $WshShell = 
            New-Object -ComObject WScript.Shell

        switch ($WindowStyle) {
            'Default' {
                $_windowStyle = 1
                break
            }

            'Maximized' {
                $_windowStyle = 3
                break
            }

            'Minimized' {
                $_windowStyle = 7
                break
            }

            Default {
                $_windowStyle = 1
                break
            }
        }

        $WshShortcut = 
            $WshShell.CreateShortcut($ShortcutPath)

        if ($Arguments) {$WshShortcut.Arguments = $Arguments}
        if ($Description) {$WshShortcut.Description = $Description}
        if ($FullName) {$WshShortcut.FullName = $FullName}
        if ($HotKey) {$WshShortcut.Hotkey = $HotKey}
        if ($IconLocationPath) {$WshShortcut.IconLocation = "$IconLocationPath`, $IconLocationIndex"}
        if ($RelativePath) {$WshShortcut.RelativePath = $RelativePath}

        $WshShortcut.TargetPath = $TargetPath

        if ($WindowStyle) {$WshShortcut.WindowStyle = $_windowStyle}
        if ($WorkingDirectory) {$WshShortcut.WorkingDirectory = $WorkingDirectory}

        $WshShortcut.Save()
    }

    End {}
}

function Out-Prepend {
    <#
        .SYNOPSIS
        Prepends content to file.

        .DESCRIPTION
        This basically works like Out-File -Append but prepends content to the top of a file.

        .PARAMETER InputObject
        The input object is designed to be piped output from other commands.

        .PARAMETER Path
        This is the file which will have the content prepended.

        .EXAMPLE

        Out-Prepend -InputObject Get-Date -Path .\dates.txt
        PS C:\>Out-Prepend -InputObject (ls) -Path .\dates.txt

        .INPUTS
        [System.Object]

        .OUTPUTS
        None
    #>


    Param(

        [Parameter(
            ValueFromPipeline = $false,
            Position = 0
        )]
        [system.object]$InputObject,

        [Parameter(
            Mandatory=$true,
            Position=1
        )]
        $Path
    )

    Begin {

        if (!(Test-Path $Path)) {

            Write-Warning "File not found: `"$Path`""
            Write-Verbose "Creating new file: `"$Path`"..."
            New-Item -Path $Path -Force

        }

    }

    Process {
        
        $_newContent = $InputObject
        $_existingContent = Get-Content $Path

        $_newContent | Out-File $Path
        $_newContentWritten = $?

        $_existingContent | Out-File $Path -Append
        $_existingContentWritten = $?

        if ($_existingContentWritten -and $_newContentWritten) {

            Write-Verbose 'Content prepended successfully.'

        } else {

            if (!$_existingContentWritten) {Write-Warning "Failed to write existing content."}
            if (!$_newContentWritten) {Write-Warning "Failed to prepend new content."}

        }

    }

    End {}

}

function Restart-Module {
    <#
        .SYNOPSIS
        Reload a PowerShell module
        
        .DESCRIPTION
        Removes and imports a PowerShell module. Just supply the module name.
        
        .PARAMETER Name
        Enter the name of a loaded module.
        
        .INPUST
        None
        
        .OUTPUTS
        None
        
    #>

    
    [CmdletBinding()]

    Param(
        [Parameter(Mandatory=$true)]
        $Name
    )

    Begin{
        
        $_verbose = @{}
        
        if (
        
            $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent

        ) {
        
            $_verbose = @{Verbose=$true}

        }
    }

    Process{

        Remove-Module -Name $Name @_verbose

        Import-Module -Name $Name @_verbose

    }

    End{}

}

function Test-Layer4Port {
    <#
        .SYNOPSIS
            Tests port on computer.
     
        .DESCRIPTION
            Tests port on computer.
      
        .PARAMETER ComputerName
            Name of server to test the port connection on.
       
        .PARAMETER Port
            Port to test
        
        .PARAMETER TCP
            Use tcp port
       
        .PARAMETER UDP
            Use udp port
      
        .PARAMETER UdpTimeOut
            Sets a timeout for UDP port query. (In milliseconds, Default is 1000)
       
        .PARAMETER TcpTimeOut
            Sets a timeout for TCP port query. (In milliseconds, Default is 1000)
                  
        .NOTES
            Name: Test-Port.ps1
            Author: Boe Prox
            DateCreated: 18Aug2010
            List of Ports: http://www.iana.org/assignments/port-numbers
       
            To Do:
                Add capability to run background jobs for each host to shorten the time to scan.
        .LINK
            https://boeprox.wordpress.org
      
        .EXAMPLE
            Test-Port -ComputerName 'server' -Port 80
            Checks port 80 on server 'server' to see if it is listening
     
        .EXAMPLE
            'server' | Test-Port -Port 80
            Checks port 80 on server 'server' to see if it is listening
       
        .EXAMPLE
            Test-Port -ComputerName @("server1","server2") -Port 80
            Checks port 80 on server1 and server2 to see if it is listening
     
        .EXAMPLE
            Test-Port -ComputerName dc1 -Port 17 -UDP -UdpTimeOut 10000
     
            Server : dc1
            Port : 17
            TypePort : UDP
            Open : True
            Notes : "My spelling is Wobbly. It's good spelling but it Wobbles, and the letters
                    get in the wrong places." A. A. Milne (1882-1958)
     
            Description
            -----------
            Queries port 17 (qotd) on the UDP port and returns whether port is open or not
        
        .EXAMPLE
            @("server1","server2") | Test-Port -port 80
            Checks port 80 on server1 and server2 to see if it is listening
       
        .EXAMPLE
            (Get-Content hosts.txt) | Test-Port -port 80
            Checks port 80 on servers in host file to see if it is listening
      
        .EXAMPLE
            Test-Port -ComputerName (Get-Content hosts.txt) -port 80
            Checks port 80 on servers in host file to see if it is listening
         
        .EXAMPLE
            Test-Port -ComputerName (Get-Content hosts.txt) -port @(1..59)
            Checks a range of ports from 1-59 on all servers in the hosts.txt file
             
    #>


    [cmdletbinding(  
        DefaultParameterSetName = 'tcp',  
        ConfirmImpact = 'low'  
    )]  

    Param(  

        [Parameter(  
            Mandatory = $True,  
            Position = 0,  
            ParameterSetName = '',  
            ValueFromPipeline = $True
        )]  
        [array]
        $ComputerName,  

        [Parameter(  
            Position = 1,  
            Mandatory = $True,  
            ParameterSetName = ''
        )]  
        [array]
        $Port,    
               
        [Parameter(  
            Mandatory = $False,  
            ParameterSetName = 'tcp'
        )]  
        [switch]
        $TCP,  

        [Parameter(  
            Mandatory = $False,  
            ParameterSetName = 'tcp'
        )]  
        [int]
        $TcpTimeOut=1000,  

        [Parameter(  
            Mandatory = $False,  
            ParameterSetName = 'udp'
        )]  
        [switch]
        $UDP,  

        [Parameter(  
            Mandatory = $False,  
            ParameterSetName = 'udp'
        )]  
        [int]
        $UdpTimeout=1000                                      

    )  

    Begin {  

        If (!$TCP -AND !$UDP) {$TCP = $True}  
        #Typically you never do this, but in this case I felt it was for the benefit of the function
        #as any errors will be noted in the output of the report
        $ErrorActionPreference = "SilentlyContinue"  
        $_report = @()  

    }  

    Process {     

        ForEach ($_computer in $ComputerName) {  

            ForEach ($_port in $Port) {  

                If ($TCP) {    

                    #Create temporary holder
                    $temp = "" | Select Server, Port, TypePort, Open, Notes  

                    #Create object for connecting to port on computer
                    $tcpobject = new-Object system.Net.Sockets.TcpClient  

                    #Connect to remote machine's port
                    $connect = $tcpobject.BeginConnect($_computer,$_port,$null,$null)  

                    #Configure a timeout before quitting
                    $wait = $connect.AsyncWaitHandle.WaitOne($TcpTimeOut,$false)  

                    #If timeout
                    If(!$wait) {  

                        #Close connection
                        $tcpobject.Close()  
                        Write-Verbose "Connection Timeout"  

                        #Build report
                        $temp.Server = $_computer  
                        $temp.Port = $_port  
                        $temp.TypePort = "TCP"  
                        $temp.Open = "False"  
                        $temp.Notes = "Connection to Port Timed Out"  

                    } Else {  

                        $error.Clear()  
                        $tcpobject.EndConnect($connect) | out-Null  

                        #If error
                        If($error[0]){  

                            #Begin making error more readable in report
                            [string]$string = ($error[0].exception).message  
                            $message = (($string.split(":")[1]).replace('"',"")).TrimStart()  
                            $failed = $true  

                        }  

                        #Close connection
                        $tcpobject.Close()  

                        #If unable to query port to due failure
                        If($failed){  

                            #Build report
                            $temp.Server = $_computer  
                            $temp.Port = $_port  
                            $temp.TypePort = "TCP"  
                            $temp.Open = "False"  
                            $temp.Notes = "$message"  

                        } Else{  

                            #Build report
                            $temp.Server = $_computer  
                            $temp.Port = $_port  
                            $temp.TypePort = "TCP"  
                            $temp.Open = "True"    
                            $temp.Notes = ""  

                        }  
                    }     

                    #Reset failed value
                    $failed = $Null      

                    #Merge temp array with report
                    $_report += $temp  

                }   
               
                If ($UDP) {  

                    #Create temporary holder
                    $temp = "" | Select Server, Port, TypePort, Open, Notes                                     

                    #Create object for connecting to port on computer
                    $udpobject = new-Object system.Net.Sockets.Udpclient

                    #Set a timeout on receiving message
                    $udpobject.client.ReceiveTimeout = $UdpTimeout 

                    #Connect to remote machine's port
                    Write-Verbose "Making UDP connection to remote server" 
                    $udpobject.Connect("$_computer",$_port) 

                    #Sends a message to the host to which you have connected.
                    Write-Verbose "Sending message to remote host" 
                    $a = new-object system.text.asciiencoding 
                    $byte = $a.GetBytes("$(Get-Date)") 
                    [void]$udpobject.Send($byte,$byte.length) 

                    #IPEndPoint object will allow us to read datagrams sent from any source.
                    Write-Verbose "Creating remote endpoint" 
                    $remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any,0) 

                    Try { 

                        #Blocks until a message returns on this socket from a remote host.
                        Write-Verbose "Waiting for message return" 
                        $receivebytes = $udpobject.Receive([ref]$remoteendpoint) 
                        [string]$returndata = $a.GetString($receivebytes)

                        If ($returndata) {

                            Write-Verbose "Connection Successful"  

                            #Build report
                            $temp.Server = $_computer  
                            $temp.Port = $_port  
                            $temp.TypePort = "UDP"  
                            $temp.Open = "True"  
                            $temp.Notes = $returndata   
                            $udpobject.close()   

                        }   
                                        
                    } Catch { 

                        If ($Error[0].ToString() -match "\bRespond after a period of time\b") { 

                            #Close connection
                            $udpobject.Close()  

                            #Make sure that the host is online and not a false positive that it is open
                            If (Test-Connection -comp $_computer -count 1 -quiet) { 

                                Write-Verbose "Connection Open"  

                                #Build report
                                $temp.Server = $_computer  
                                $temp.Port = $_port  
                                $temp.TypePort = "UDP"  
                                $temp.Open = "True"  
                                $temp.Notes = "" 

                            } Else { 

                                <#
                                It is possible that the host is not online or that the host is online,
                                but ICMP is blocked by a firewall and this port is actually open.
                                #>
 
                                Write-Verbose "Host maybe unavailable"  

                                #Build report
                                $temp.Server = $_computer  
                                $temp.Port = $_port  
                                $temp.TypePort = "UDP"  
                                $temp.Open = "False"  
                                $temp.Notes = "Unable to verify if port is open or if host is unavailable."                                 

                            }   
                                              
                        } ElseIf (
                            $Error[0].ToString() -match "forcibly closed by the remote host" 
                        ) { 

                            #Close connection
                            $udpobject.Close()  
                            Write-Verbose "Connection Timeout"  

                            #Build report
                            $temp.Server = $_computer  
                            $temp.Port = $_port  
                            $temp.TypePort = "UDP"  
                            $temp.Open = "False"  
                            $temp.Notes = "Connection to Port Timed Out"                         

                        } Else {                      

                            $udpobject.close() 

                        } 
                    }     

                    #Merge temp array with report
                    $_report += $temp 

                }                                  
            }  
        }                  
    }  

    End {  
        #Generate Report
        $_report
    }
}

function Update-RyanToolsModule {
    <#
        
        .SYNOPSIS
        Updates the modules Ryan Tools and Ryan Tools Admin.

        .DESCRIPTION
        This command will update the modules Ryan Tools and Ryan Tools Admin
        from a public Bitbucket repository at:
        
        https://bitbucket.org/Rhyknowscerious/ryan_tools

        Basically it will download, extract, and move these modules to an
        appropriate location for PowerShell to automatically load the module
        upon use.

        If you want to install the modules yourself, just download the modules and put
        them in the appropriate directories

        .PARAMETER Force
        This switch will enable over-writing files and automatically creating necessary folders if missing.

        .PARAMETER NoCleanup
        Normally, this command will delete the downloaded archive and extracted temporary files. Use this switch to keep these files instead.

        .PARAMETER ModuleArchiveDownloadUrl
        This is where the module zip file is on BitBucket.

        .PARAMETER ModuleArchiveDownloadFilePath
        This is where the downloaded zip file will be saved before extraction.

        .PARAMETER ModuleExtractionDirectory
        This is where the modules will be stored temporarily before extraction.

        .PARAMETER ModuleInstallDirectory
        This tells the command where to install the updated module.

        .INPUTS
        None

        .OUTPUTS
        None
    #>


    [CmdletBinding()]

    Param(
            
        [switch]$Force,

        [switch]$NoCleanup,
        
        $ModuleArchiveDownloadUrl,

        $ModuleArchiveDownloadFilePath = 
            "$HOME\Downloads\RyanToolsModuleArchive.zip",

        $ModuleExtractionDirectory = 
            "$HOME\Downloads\RyanToolsModuleArchiveExtracted\"
    )

    DynamicParam {
           
        $_nl = [System.Environment]::NewLine
        # Create the dynamic parameter name, dictionary, attribute collection
        $_dynamicParameterName = 'ModuleInstallDirectory'
        $_runtimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $_attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            
        # Create and set the parameters' attributes
        $_parameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $_parameterAttribute.Mandatory = $true

        # Create, generate, and set the ValidateSet
        $_validateSet = $env:PSModulePath -split ';'
        #$_validateSet += "$HOME\Downloads\PowerShellModules"
        #$_validateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($_validateSet)
        
        # Create parameter attribute: help message
        $_helpMessage = 
            "$_nl`Here are configured powershell module installation " + 
            "locations on for this computer but you can choose whatever you " + 
            "want:$_nl$_nl" + 
            ($_validateSet -join $_nl) + $_nl+$_nl + 
            "You should usually put modules for specific users to access in " + 
            "`"`$HOME\Documents\WindowsPowerShell`". Modules for all users " + 
            "typically go into `"`$env:ProgramFiles\WindowsPowerShell\Modules`" " + 
            "or `"`${env:ProgramFiles(x86)}\WindowsPowerShell\Modules`" " + 
            "And system modules typically go in `"`$PSHOME\Modules`"."+$_nl+$_nl

        $_parameterAttribute.HelpMessage = $_helpMessage

        # Add the Parameter and ValidateSet attributes to the attributes collection
        $_attributeCollection.Add($_parameterAttribute)
        $_attributeCollection.Add($_validateSetAttribute)

        # Create the dynamic parameter
        $_runtimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter(
            $_dynamicParameterName,
            [string],
            $_attributeCollection
        )

        # Add the dynamic parameter to a collection and return them
        $_runtimeParameterDictionary.Add(
            $_dynamicParameterName,
            $_runtimeParameter
        )

        return $_runtimeParameterDictionary
        
    }

    begin {
        
        # Bind the parameter to a friendly variable
        $ModuleInstallDirectory = $PsBoundParameters[$_dynamicParameterName]

        $_verbose = @{}

        $_force = @{}

        if (

            $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent

        ) {

            $_verbose = @{Verbose=$true}

        }

        if ($Force.IsPresent) { $_force = @{Force=$true} }

        if (!$ModuleArchiveDownloadUrl) {

            [Net.ServicePointManager]::SecurityProtocol = 
                [Net.SecurityProtocolType]::Tls12

            $_html = 
                Invoke-WebRequest `
                    -Uri "https://bitbucket.org/Rhyknowscerious/ryan_tools/downloads/" `
                    @_verbose

            $_htmlAsArray = 
                $_html.RawContent.Split([System.Environment]::NewLine)

            $_htmlTargetLine = $_htmlAsArray | sls '\.zip'
            
            $_url = "https://www.bitbucket.org"

            $_url += $_htmlTargetLine.ToString().trim() -replace 
                '^.*href="','' -replace 
                '">Download reposit.*$',''

            $ModuleArchiveDownloadUrl = $_url

        }
    }

    process{

        $_updateStartTime = Get-Date

        [Net.ServicePointManager]::SecurityProtocol = 
            [Net.SecurityProtocolType]::Tls12

        $_webClient = New-Object System.Net.WebClient @_verbose

        Write-Verbose "Attempting to download module `"$ModuleArchiveDownloadUrl`" to `"$ModuleArchiveDownloadFilePath`". Please wait...$_nl$_nl"

        $_downloadStartTime = Get-Date

        $_webClient.DownloadFile(
            $ModuleArchiveDownloadUrl, 
            $ModuleArchiveDownloadFilePath
        )
        
        $_downloadSucceeded = $?

        $_downloadEndTime = Get-Date

        if ($_downloadSucceeded) {

            Write-Verbose "Module downloaded from `"$ModuleArchiveDownloadUrl`" saved: `"$ModuleArchiveDownloadFilePath`"$_nl$_nl"
        
        } else {
        
            Write-Error "Module update failed. Download failed: `"$ModuleArchiveDownloadUrl`""

            return
        
        }
    
        $_downloadTimeSpan = New-TimeSpan $_downloadStartTime $_downloadEndTime

        Write-Verbose "Download duration: $_downloadTimeSpan$_nl$_nl"

        Write-Verbose "Extracting $ModuleArchiveDownloadFilePath to `"$ModuleExtractionDirectory`"...$_nl$_nl"

        Expand-Archive `
            -Path $ModuleArchiveDownloadFilePath `
            -DestinationPath "$ModuleExtractionDirectory" `
            @_force `
            @_verbose

        $_extractedSuccessfully = $?

        if ($_extractedSuccessfully) {
        
            Write-Verbose "Extraction successful.$_nl$_nl"

        } else {
        
            Write-Error "Module update failed. Extraction failed: `"$ModuleArchiveDownloadFilePath`""

            return

        }

        $_updatedModulesFolder = (
            Get-ChildItem $ModuleExtractionDirectory
        ).FullName

        $_updatedModules = (
            Get-ChildItem $_updatedModulesFolder -Directory
        ).FullName

        Write-Verbose "Copying modules from `"$_updatedModulesFolder`" to `"$ModuleInstallDirectory...$_nl$_nl"
        
        if (!(Test-Path $ModuleInstallDirectory)) {
            
            Write-Verbose "Folder doesn't exist `"$ModuleInstallDirectory`"$_nl$_nl"

            New-Item `
                -ItemType Directory `
                -Path $ModuleInstallDirectory `
                @_verbose

            if (!$?) {

                Write-Error "Could not create directory $"

                return
            }
        }
            
        Copy-Item `
            -Path $_updatedModules `
            -Destination $ModuleInstallDirectory `
            -Recurse `
            -Container `
            @_verbose `
            @_force

        $_filesCopiedSuccesfully = $?

        if ($_filesCopiedSuccesfully) {
            
            $_updateEndTime = Get-Date

            $_updateTimeSpan = New-TimeSpan $_updateStartTime $_updateEndTime

        } else {

            Write-Error "Module update failed: Error copying files from `"$_updatedModulesFolder`" to `"$ModuleInstallDirectory`"."
            return
        }

        Write-Verbose "Module updated successfully. Update duration: $_updateTimeSpan$_nl$_nl"
        
        if ($NoCleanup.IsPresent) {
            
            Write-Verbose "Temporary files NOT removed: $_nl`t$ModuleArchiveDownloadUrl$_nl`t$ModuleExtractionDirectory$_nl$_nl"

        } else {

            Write-Verbose "Cleaning up downloaded files.$_nl$_nl"

            Remove-Item `
                -Path $ModuleArchiveDownloadFilePath,$ModuleExtractionDirectory `
                -Recurse `
                @_force `
                @_verbose

            $_tempFile = ls $ModuleInstallDirectory -Filter "*Rhyknowscerious*"

            if ($_tempFile.FullName -match "Rhyknowscerious") {
                
                Remove-Item `
                    -Path $_tempFile.FullName `
                    -Recurse `
                    @_force `
                    @_verbose

            }
        }
    }

    End {
        
    }
}

function Invoke-WpfXamlForm {

    [CmdletBinding(
        DefaultParameterSetName="Xaml As String, Logic As Scriptblock"
    )]

    <##>

    Param(
    
        [Parameter(
            ParameterSetName="Xaml As String, Logic As File",
            Mandatory=$true
        )]
        [Parameter(
            ParameterSetName="Xaml As String, Logic As Scriptblock",
            Mandatory=$true
        )]
        [string]
        $XamlString,

        [Parameter(
            ParameterSetName="Xaml As File, Logic As File",
            Mandatory=$true
        )]
        [Parameter(
            ParameterSetName="Xaml As File, Logic As Scriptblock",
            Mandatory=$true
        )]
        $XamlPath,
        
        [Parameter(
            ParameterSetName="Xaml As String, Logic As File",
            Mandatory=$true
        )]
        [Parameter(
            ParameterSetName="Xaml As File, Logic As File",
            Mandatory=$true
        )]
        [string]
        $LogicPath,
        
        [Parameter(
            ParameterSetName="Xaml As String, Logic As Scriptblock",
            Mandatory=$true
        )]
        [Parameter(
            ParameterSetName="Xaml As File, Logic As Scriptblock",
            Mandatory=$true
        )]
        [scriptblock]
        $LogicScriptblock
    )

    DynamicParam{}

    Begin{
    
        Add-Type -AssemblyName PresentationFramework

        if ($XamlString) { [xml]$_xaml = $XamlString }

        if ($XamlPath) { [xml]$_xaml = Get-Content $XamlPath }
        
        $_reader = (New-Object System.Xml.XmlNodeReader $_xaml)
        
        $_window = [Windows.Markup.XamlReader]::Load($_reader)
        
    }

    Process{
        
        if ($LogicPath) {. $LogicPath}

        if ($LogicScriptblock) {$LogicScriptblock}

    }

    End{

        $_window.ShowDialog()

    }

}

function Invoke-AdoSqlDataReader {

    <#
        .SYNOPSIS
        Invokes an ADO.NET SqlDataReader instance.

        .DESCRIPTION
        Uses an ADO.NET SqlDataReader instance to capture query output and formats it as a psobject.

        .PARAMETER ComputerName
        The name or ip address of your SQL Server.

        .PARAMETER DatabaseName
        (Alias: InitialCatalog)
        The name of the database aka initial catalog

        .PARAMETER Query
        Enter an SQL query as a string which is sent to the SqlDataAdapter(String,SqlConnection) constructor overload.

        .PARAMETER IntegratedSecurity
        Sets the SQL connection string keyword "Integrated Security" to SSPI and uses Windows Authentication.

        .PARAMETER Credential
        Enter SQL Server credentials instead of Integrated Security aka Windows Authentication.
        Careful, this may not actually be secure.

        .INPUTS
        None

        .OUTPUTS
        System.Management.Automation.PsObject
    #>


    [CmdletBinding(
        DefaultParameterSetName="IntegratedSecurity"
    )]

    Param(

        [Parameter(
            ParameterSetName="IntegratedSecurity"
        )]
        [Parameter(
            ParameterSetName="UseSqlCredentials"
        )]
        [Alias("Server","cn")]
        $ComputerName,

        [Parameter(
            ParameterSetName="IntegratedSecurity",
            Mandatory=$true
        )]
        [Parameter(
            ParameterSetName="UseSqlCredentials",
            Mandatory=$true
        )]
        [Alias("InitialCatalog","Database")]
        $DatabaseName,
        
        [Parameter(
            ParameterSetName="IntegratedSecurity"
        )]
        [Parameter(
            ParameterSetName="UseSqlCredentials"
        )]
        [Alias("q")]
        $Query,
        
        [Parameter(
            ParameterSetName="IntegratedSecurity"
        )]
        [switch]
        [Alias("i")]
        $IntegratedSecurity,
        
        [Parameter(
            ParameterSetName="UseSqlCredentials",
            Mandatory=$true
        )]
        [Alias("User","u")]
        $Credential

    )

    $_connection = $null

    $_dataReader = $null

    $_connectionArray = @()

    if ($ComputerName) {

        $_connectionArray += "Data Source='$ComputerName';"

    } else {

        $_connectionArray += "Data Source=(local);"

    }

    $_connectionArray += @(

        "Initial Catalog='$DatabaseName';"

    )

    if ($Credential) {
        
        $Credential = Get-Credential $Credential
        
        if (!$?) {
            
            return

        }

        $_connectionArray += @(
            
            "User ID=$($Credential.UserName);",

            "Password=$($Credential.GetNetworkCredential().Password);"

        )


    } else {
        
        $_connectionArray += "Integrated Security=SSPI;"
    }

    $_connectionString = 
        -join $_connectionArray

    Write-Verbose "Using connection string: `"$_connectionString`""


    $_connection = 
        [System.Data.SqlClient.SqlConnection]::new(
            $_connectionString
        )

    try {
        
        Write-Verbose "Opening connection to database `"$DatabaseName`" on SQL Server `"$ComputerName`"..."

        $_connection.Open()
    
        $_command = 
            [System.Data.SqlClient.SqlCommand]::new(

                [string]$Query,

                $_connection

            )

        Write-Verbose "Initializing DataReader..."

        $_dataReader = $_command.ExecuteReader()

        $_table = @()

        while (

            $_dataReader.Read()

        ) {

            $_row = New-Object psobject

            for ($i=0; $i -lt $_dataReader.FieldCount; $i++){

                $_row | Add-Member `
                    -MemberType NoteProperty `
                    -Name $_dataReader.GetName($i)`
                    -Value $_dataReader.GetValue($i)
            }

            $_table += $_row

        }


    } finally {

        Write-Verbose "Terminating DataReader"

        if ($_dataReader -ne $null) { $_dataReader.Close() }

        Write-Verbose "Closing connection to database `"$DatabaseName`" on SQL Server `"$ComputerName`"..."

        if ($_connection -ne $null) { $_connection.Close() }

    }

    return $_table
}

function Get-AdoDataSet {

    <#
        .SYNOPSIS
        Gets an ADO.NET DataSet instance.

        .DESCRIPTION
        Uses ADO Classes SqlConnection,SqlDataAdapter,SqlCommandBuilder to return an ADO.NET DataSet instance.

        .PARAMETER ComputerName
        The name or ip address of your SQL Server.

        .PARAMETER DatabaseName
        (Alias: InitialCatalog)
        The name of the database aka initial catalog

        .PARAMETER Query
        Enter an SQL query as a string which is sent to the SqlDataAdapter(String,SqlConnection) constructor overload.

        .PARAMETER IntegratedSecurity
        Sets the SQL connection string keyword "Integrated Security" to SSPI and uses Windows Authentication.

        .PARAMETER Credential
        Enter SQL Server credentials instead of Integrated Security aka Windows Authentication.
        Careful, this may not actually be secure.

        .INPUTS
        None

        .OUTPUTS
        System.Data.Dataset

        .LINK
        https://docs.microsoft.com/en-us/dotnet/api/system.data.dataset
    #>


    [CmdletBinding(
        DefaultParameterSetName="IntegratedSecurity"
    )]

    Param(

        [Parameter(
            ParameterSetName="IntegratedSecurity"
        )]
        [Parameter(
            ParameterSetName="UseSqlCredentials"
        )]
        [Alias("Server","cn")]
        $ComputerName,

        [Parameter(
            ParameterSetName="IntegratedSecurity",
            Mandatory=$true
        )]
        [Parameter(
            ParameterSetName="UseSqlCredentials",
            Mandatory=$true
        )]
        [Alias("InitialCatalog","Database")]
        $DatabaseName,
        
        [Parameter(
            ParameterSetName="IntegratedSecurity"
        )]
        [Parameter(
            ParameterSetName="UseSqlCredentials"
        )]
        [Alias("q")]
        $Query,
        
        [Parameter(
            ParameterSetName="IntegratedSecurity"
        )]
        [switch]
        [Alias("i")]
        $IntegratedSecurity,
        
        [Parameter(
            ParameterSetName="UseSqlCredentials",
            Mandatory=$true
        )]
        [Alias("User","u")]
        $Credential

    )

    $_connectionArray = @()

    if ($ComputerName) {

        $_connectionArray += "Data Source='$ComputerName';"

    } else {

        $_connectionArray += "Data Source=(local);"

    }

    $_connectionArray += @(

        "Initial Catalog='$DatabaseName';"

    )

    if ($Credential) {
        
        $Credential = Get-Credential $Credential
        
        if (!$?) {
            
            return

        }

        $_connectionArray += @(
            
            "User ID=$($Credential.UserName);",

            "Password=$($Credential.GetNetworkCredential().Password);"

        )


    } else {
        
        $_connectionArray += "Integrated Security=SSPI;"
    }

    $_connectionString = 
        -join $_connectionArray

    Write-Verbose "Using connection string: `"$_connectionString`""

    $_connection = 
        $null

    $_connection = 
        [System.Data.SqlClient.SqlConnection]::new(
            $_connectionString
        )

    $_dataSet = 
        [System.Data.DataSet]::new()
    
    Write-Verbose "Initializing DataAdapter..."

    $_dataAdapter = 
        [System.Data.SqlClient.SqlDataAdapter]::new(
    
            $Query,

            $_connection

        )

    $_commandBuilder = 
        [System.Data.SqlClient.SqlCommandBuilder]::new(
        
            $_sqlDataAdapter

        )

    Write-Verbose "Filling DataSet..."

    $_dataAdapter.Fill(
    
        $_dataSet

    )

    if ($?) {

        return $_dataSet

    }

}