General.psm1

#region Classes
add-type @'
namespace CPolydorou.General
{
    public class UpTime
    {
        public string Name;
        public int Days;
        public int Hours;
        public int Minutes;
        public int Seconds;
        public int Milliseconds;
    }
}
'@


add-type @'
namespace CPolydorou.General
{
    public class LoggedOnUser
    {
        public string Username;
        public int SessionID;
        public string SessionName;
        public string SessionState;
        public System.TimeSpan SessionIdleTime;
        public System.DateTime LogonTime;
    }
}
'@


#endregion

#region Functions
#region ForEach-Object-Parallel
function ForEach-Object-Parallel
{
    <#
    .SYNOPSIS
        This function allows the user to run multiple jobs in parallel.
    .EXAMPLE
        (0..50) |ForEach-Parallel -MaxThreads 4{
            $_
            sleep 3
        }
    #>


    param(
        [Parameter(Mandatory=$true,position=0)]
        [System.Management.Automation.ScriptBlock] $ScriptBlock,
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [PSObject]$InputObject,
        [Parameter(Mandatory=$false)]
        [int]$MaxThreads=5,
        [Parameter(Mandatory=$false)]
        [hashtable]$Parameters
    )
    BEGIN {
        $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
        $pool = [Runspacefactory]::CreateRunspacePool(1, $maxthreads, $iss, $host)
        $pool.open()
        $threads = @()
        $scriptBlockString = "param(`$_"
        if($Parameters -ne $null)
        {
            $Parameters.Keys |
                %{
                    $scriptBlockString += ",`$$_"
                }        
        }
        $scriptBlockString += ")`r`n"
        $scriptBlockString += $Scriptblock.ToString()
        $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock($scriptBlockString)
    }
    PROCESS {
        $powershell = [powershell]::Create().addscript($scriptblock).addargument($InputObject)

        if($Parameters -ne $null)
        {
            $Parameters.Keys |
                %{
                    $powershell = $powershell.AddArgument($Parameters[$_])
                }
        }
        $powershell.runspacepool=$pool
        $threads+= @{
            instance = $powershell
            handle = $powershell.begininvoke()
        }
    }
    END {
        $notdone = $true
        while ($notdone) {
            $notdone = $false
            for ($i=0; $i -lt $threads.count; $i++) {
                $thread = $threads[$i]
                if ($thread) {
                    if ($thread.handle.iscompleted) {
                        $thread.instance.endinvoke($thread.handle)
                        $thread.instance.dispose()
                        $threads[$i] = $null
                    }
                    else {
                        $notdone = $true
                    }
                }
            }
        }
    }
}
#endregion

#region Get-ConsoleUser
function Get-ConsoleUser
{
<#
.SYNOPSIS
    This function gets the username of the user logged on on a computer.
.EXAMPLE
    Get-ConsoleUser -computername host1
#>


    Param(
        [Parameter(ParameterSetName = "ComputerName", Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]] $ComputerName,
        [system.management.automation.psCredential]$Credential
        )

    Process
    {
        if( $ComputerName.Count -eq 0 )
        {
            if($Credential)
            {
                Get-WMIObject -class Win32_ComputerSystem -Credential $Credential | Select-Object Username
            }
            else
            {
                Get-WMIObject -class Win32_ComputerSystem | Select-Object Username
            }

        }

        else
        {
            foreach( $c in $ComputerName)
            {
                if($Credential)
                {
                    Get-WMIObject -ComputerName $c -class Win32_ComputerSystem -Credential $Credential | Select-Object Username
                }
                else
                {
                    Get-WMIObject -ComputerName $c -class Win32_ComputerSystem | Select-Object Username
                }

            }
        }
    }

}
#endregion

#region Test-IsAdmin
Function Test-IsAdmin   
{  
<#
.SYNOPSIS
   Function used to detect if current user is an Administrator.
      
.DESCRIPTION
   Function used to detect if current user is an Administrator.
             
.EXAMPLE
    Test-IsAdmin
       
    
Description
-----------
Command will check the current user to see if an Administrator.
#>
  
    [cmdletbinding()]  
    Param()  
      
# Write-Verbose "Checking to see if current user context is Administrator"
    If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))  
    {  
        return $false
    }  
    Else   
    {  
        return $true
    }  
}
#endregion

#region Out-Speech
Function Out-Speech()
{
<#
.SYNOPSIS
   Function used to read strings.
      
.EXAMPLE
    Out-Speech -Text "Read me!".
       
#>
  

    Param(
        [string[]] $Text
        )

    $v = New-Object -ComObject SAPI.SPVoice

    foreach( $s in $Text )
    {
        $v.Speak($s) 
    }
}
#endregion

#region Get-PrinterLegacy
function Get-PrinterLegacy
{
<#
    .SYNOPSIS
     Displays the list of installed printers.
    .DESCRIPTION
     This function lists all locally installed printers on local computer or a remote one.
     It uses WMI and not the printer functions added after Windows 7 which makes it very usefull on older systems.
    .EXAMPLE
     Get-PrinterLegacy
     This command returns a list of all locally installed printers.
    .EXAMPLE
     Get-PrinterLegacy -ComputerName client01
     This command returns a list of all locally installed printers on computer 'client01'.
#>

    Param
    (
        [string]$ComputerName = ".",
        [system.management.automation.psCredential]$Credential
    )

    if($Credential)
    {
        Get-WMIObject -Class Win32_Printer -ComputerName $ComputerName -Credential $Credential | Select-Object -Property Name, SystemName | Format-Table
    }
    else
    {
        Get-WMIObject -Class Win32_Printer -ComputerName $ComputerName | Select-Object -Property Name, SystemName | Format-Table
    }
}
#endregion

#region Get-PowerEvent
function Get-PowerEvent
{
<#
    .SYNOPSIS
     Displays the list of power events.
    .DESCRIPTION
     This function lists all power events on local computer.
     Power events are "Restart" and "Power off".
    .EXAMPLE
     Get-PowerEvent
     This command returns a list of power events.
    .EXAMPLE
     Get-PowerEvent | Where-Object {$_.Action -eq "Reboot"}
     This command returns all reboot events.
#>


    Param(
        [Parameter(ParameterSetName = "ComputerName", Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]] $ComputerName,
        [system.management.automation.psCredential]$Credential

        )

    Process
    {
        # Varible to hold the events
        $events = @()

        if( $ComputerName.Count -eq 0 )
        {
            if($Credential)
            {
                $events = Get-WinEvent -FilterHashtable @{logname='System'; id=1074} -Credential $Credential
            }
            else
            {
                $events = Get-WinEvent -FilterHashtable @{logname='System'; id=1074}
            }
        }
        else
        {
            foreach( $c in $ComputerName)
            {
                if($Credential)
                {
                    $events += Get-WinEvent -FilterHashtable @{logname='System'; id=1074} -ComputerName $c -Credential $Credential
                }
                else
                {
                    $events += Get-WinEvent -FilterHashtable @{logname='System'; id=1074} -ComputerName $c
                }
            }
        }

        $events | 
            ForEach-Object{
                $rv = New-Object PSObject | Select-Object Date, User, Action, Process, Reason, ReasonCode, Comment
                $rv.Date = $_.TimeCreated
                $rv.User = $_.Properties[6].Value
                $rv.Process = $_.Properties[0].Value
    # $rv.Action = $_.Properties[4].Value
                $rv.Action = $_.Properties[4].Value.substring(0,1).toupper() + $_.Properties[4].Value.substring(1).tolower()
                $rv.Reason = $_.Properties[2].Value
                $rv.ReasonCode = $_.Properties[3].Value
                $rv.Comment = $_.Properties[5].Value
                $rv
            } |
                Sort-Object -Property Date -Descending |
                    Select-Object Date, Action, Reason, User |
                        Format-Table
    }
}
#endregion

#region Get-StartupApplications
Function Get-StartupApplications
{
<#
    .SYNOPSIS
     Displays the list of applications that run on startup.
    .DESCRIPTION
     This function lists all applications that run on startup.
    .EXAMPLE
     Get-StartupApplications
     This command returns the list of applications that start on computer start.
    .EXAMPLE
     Get-StartupApplications -ComputerName client1
     This command returns all applications that run when computer client1 starts.
#>


    Param(
        [Parameter(ParameterSetName = "ComputerName", Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]] $ComputerName,
        [system.management.automation.psCredential]$Credential
        )

    Process
    {
        # Varible to hold the applications
        $apps = @()

        if( $ComputerName.Count -eq 0 )
        {
            if($Credential)
            {
                $apps = Get-WmiObject Win32_startupcommand -Credential $Credential
            }
            else
            {
                $apps = Get-WmiObject Win32_startupcommand
            }
        }
        else
        {
            foreach( $c in $ComputerName)
            {
                if($Credential)
                {
                    $apps += Get-WmiObject Win32_startupcommand -ComputerName $c -Credential $Credential
                }
                else
                {
                    $apps += Get-WmiObject Win32_startupcommand -ComputerName $c
                }
            }
        }

        $apps | 
                Sort-Object -Property Caption -Descending |
                    Select-Object Caption, User, Command |
                        Format-Table
    }
}
#endregion

#region Get-UpTime
Function Get-UpTime
{
<#
    .SYNOPSIS
     Displays the uptime for a system.
    .DESCRIPTION
     This function displays the uptime of a system.
    .EXAMPLE
     Get-UpTime
     This command displays the uptime of the system.
    .EXAMPLE
     Get-UpTime -ComputerName server1, server2
     This command displays the uptime of server1 and server2.
#>


    Param(
        [Parameter(ParameterSetName = "ComputerName", Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("Server","Name")]
        [string[]] $ComputerName,
        [system.management.automation.psCredential]$Credential
        )

    Process
    {
        # Varible to hold timestamps
        $time = @()

        if( $ComputerName.Count -eq 0 )
        {
            $wmi = New-Object PSObject
            if($Credential)
            {
                $wmi = Get-WmiObject -Class Win32_OperatingSystem -Credential $Credential
            }
            else
            {
                $wmi = Get-WmiObject -Class Win32_OperatingSystem
            }

            $time = New-Object -TypeName CPolydorou.General.UpTime
            $time.Name = $env:COMPUTERNAME

            $tmp += $wmi.ConvertToDateTime($wmi.LocalDateTime) â€“ $wmi.ConvertToDateTime($wmi.LastBootUpTime)

            $time = New-Object -TypeName CPolydorou.General.UpTime
            $time.Name = $env:COMPUTERNAME
            $time.Days = $tmp.Days
            $time.Hours =$tmp.Hours
            $time.Minutes = $tmp.Minutes
            $time.Seconds = $tmp.Seconds
            $time.Milliseconds = $tmp.Milliseconds

        }
        else
        {
            foreach( $c in $ComputerName)
            {
                $wmi = New-Object PSObject
                if($Credential)
                {
                    $wmi = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $c -Credential $Credential
                }
                else
                {
                    $wmi = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $c
                }

                $tmp = $wmi.ConvertToDateTime($wmi.LocalDateTime) â€“ $wmi.ConvertToDateTime($wmi.LastBootUpTime)

                $t = New-Object -TypeName CPolydorou.General.UpTime
                $t.Name = $c
                $t.Days = $tmp.Days
                $t.Hours =$tmp.Hours
                $t.Minutes = $tmp.Minutes
                $t.Seconds = $tmp.Seconds
                $t.Milliseconds = $tmp.Milliseconds

                $time += $t
            }
        }

        $time | 
                Sort-Object -Property Name
    }
}
#endregion

#region Get-RAMStatistics
function Get-RAMStatistics
{
 <#
    .Synopsis
     Gets information about RAM.
    .Example
     Get-RAMStatistics
     This example gets statistics about RAM on local computer.
    .Example
     Get-RAMStatistics -ComputerName server1,server2 -Credential $cred
     This example get statistics about RAM on computers server1 and server2 using the credentials saved on variable $cred using Get-Credential.
    .Description
     The Get-RAMStatistics cmdlet is used to get statistics about RAM Memory using WMI. That is the Total Physical Memory, the Free Physical Memory, the Total Virtual Memory and the Free Virtual Memory.
    .Parameter ComputerName
     The name of the computer to query.
    .Parameter Credential
     The credentials for the connection (if needed).
   #>
 
 
    Param(
        [Parameter(ParameterSetName = "ComputerName", Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]] $ComputerName,
        [system.management.automation.psCredential]$Credential
        )

    BEGIN{}

    PROCESS
    { 
        $statistics = @()

        if( $ComputerName.Count -eq 0 )
        {
            $info
            if($Credential)
            {
                $info = Get-WmiObject -Class Win32_OperatingSystem -Credential $Credential
            }
            else
            {
                $info = Get-WmiObject -Class Win32_OperatingSystem
            }

            $obj = New-Object PSObject
            $obj | Add-Member -MemberType NoteProperty -Name "Computer" -Value $env:COMPUTERNAME
            $obj | Add-Member -MemberType NoteProperty -Name "Total Physical Memory" -Value ( "{0:n0}" -f ($info.totalvisiblememorysize / 1KB))
            $obj | Add-Member -MemberType NoteProperty -Name "Free Physical Memory" -Value ( "{0:n0}" -f ($info.freephysicalmemory / 1KB))
            $obj | Add-Member -MemberType NoteProperty -Name "Total Virtual Memory" -Value ( "{0:n0}" -f ($info.totalvirtualmemorysize / 1KB))
            $obj | Add-Member -MemberType NoteProperty -Name "Free Virtual Memory" -Value ( "{0:n0}" -f ($info.freevirtualmemory /1KB))

            $statistics += $obj
        }
        else
        {
            foreach( $c in $ComputerName)
            {
                $info = New-Object PSObject
                if($Credential)
                {
                    $info = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $c -Credential $Credential
                }
                else
                {
                    $info = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $c
                }

                $obj = New-Object PSObject
                $obj | Add-Member -MemberType NoteProperty -Name "Computer" -Value $c
                $obj | Add-Member -MemberType NoteProperty -Name "Total Physical Memory" -Value ( "{0:n0}" -f ($info.totalvisiblememorysize / 1KB))
                $obj | Add-Member -MemberType NoteProperty -Name "Free Physical Memory" -Value ( "{0:n0}" -f ($info.freephysicalmemory / 1KB))
                $obj | Add-Member -MemberType NoteProperty -Name "Total Virtual Memory" -Value ( "{0:n0}" -f ($info.totalvirtualmemorysize / 1KB))
                $obj | Add-Member -MemberType NoteProperty -Name "Free Virtual Memory" -Value ( "{0:n0}" -f ($info.freevirtualmemory /1KB))

                $statistics = $statistics + $obj
            }
        }

        $statistics | 
            Sort-Object -Property Computer -Descending |
                Format-Table
    }

    END{}
}
#endregion

#region Get-ComputerDetails
function Get-ComputerDetails
{
 <#
    .Synopsis
     Gets information about operating system and bios.
    .Example
     Get-ComputerDetails
     This example gets information about local computer.
    .Example
     Get-ComputerDetails -ComputerName server1,server2 -Credential $cred
     This example gets information about computers server1 and server2 using the credentials saved on variable $cred using Get-Credential.
    .Description
     The Get-ComputerDetails cmdlet is used to get information about operating system and BIOS using WMI.
    .Parameter Credential
     The credentials used for the connection (if needed).
    .Parameter ComputerName
     The name of the computer to query
   #>
 
    Param(
        [Parameter(ParameterSetName = "ComputerName", Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]] $ComputerName,
        [system.management.automation.psCredential]$Credential
        )

    BEGIN{}

    PROCESS
    { 
        $statistics = @()

        if( $ComputerName.Count -eq 0 )
        {
            $os = New-Object PSObject
            $bios = New-Object PSObject

            if($Credential)
            {
                $os = Get-WmiObject -Class Win32_OperatingSystem -Credential $Credential
                $bios = get-wmiobject win32_bios -Credential $Credential
            }
            else
            {
                $os = Get-WmiObject -Class Win32_OperatingSystem
                $bios = get-wmiobject win32_bios
            }

                # create a new object
                $obj = new-object psobject
         
                # add properties - note that our capitalization
                # of the property names will be retained
                $obj | add-member noteproperty Computer $env:COMPUTERNAME
                $obj | add-member noteproperty BuildNumber ($os.buildnumber)
                $obj | add-member noteproperty OS ($os.caption)
                $obj | add-member noteproperty SPVersion ($os.servicepackmajorversion)
                $obj | add-member noteproperty OSArchitecture ($os.osarchitecture)
                $obj | add-member noteproperty BIOSSerial ($bios.serialnumber)

            $statistics += $obj
        }
        else
        {
            foreach( $c in $ComputerName)
            {
                $os = New-Object PSObject
                $bios = New-Object PSObject

                if($Credential)
                {
                    $os = Get-WmiObject -Class Win32_OperatingSystem -Credential $Credential -ComputerName $c
                    $bios = get-wmiobject win32_bios -Credential $Credential -ComputerName $c
                }
                else
                {
                    $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $c
                    $bios = get-wmiobject win32_bios -ComputerName $c
                }

                    # create a new object
                    $obj = new-object psobject
         
                    # add properties - note that our capitalization
                    # of the property names will be retained
                    $obj | add-member noteproperty Computer $c
                    $obj | add-member noteproperty BuildNumber ($os.buildnumber)
                    $obj | add-member noteproperty OS ($os.caption)
                    $obj | add-member noteproperty SPVersion ($os.servicepackmajorversion)
                    $obj | Add-Member NoteProperty OSArchitecture ($os.osarchitecture)
                    $obj | add-member noteproperty BIOSSerial ($bios.serialnumber)

                $statistics += $obj
            }
        }

        $statistics | 
            Sort-Object -Property Computer -Descending |
                Format-Table
    }

    END{}
}
#endregion

#region Get-SpecialFolder
function Get-SpecialFolder
{
 <#
    .Synopsis
     Gets information about the path of the user's special folders.
    .Example
     Get-SpecialFolder
     This example gets information about all special folders.
    .Example
     Get-SpecialFolder -Name Desktop
     This example gets information about the user's desktop folder.
    .Description
     The Get-SpecialFolder cmdlet is used to get information about the path of special folders.
     It is very usefull in order to test folder redirection.
    .Parameter Name
     The name of the computer to query
   #>
 

    Param(
        [Parameter(Mandatory = $false)]
        [string]$Name
        )

    $SpecialFolders = @()

    $names = [Environment+SpecialFolder]::GetNames([Environment+SpecialFolder])

    foreach($n in $names)
    {
      if($path = [Environment]::GetFolderPath($n)){
        $obj = New-Object PSObject
        $obj | Add-Member -MemberType NoteProperty -Name Name -Value $n
        $obj | Add-Member -MemberType NoteProperty -Name Path -Value $path
        $SpecialFolders += $obj
      }
    }

    if( $Name.Length -gt 0 )
    {
        $found = 0
        foreach($s in $SpecialFolders)
        {
            if( $s.Name -eq $Name )
            {
                $s
                $found = 1
                break;
            }
        }
        if( $found -eq 0 )
        {
            Write-Error "Could not find a special folder named $Name."
        }
    }
    else
    {
        $SpecialFolders
    }
}
#endregion

#region Watch-Command
function Watch-Command
{
<#
.SYNOPSIS
 
Watch the output of a command.
.DESCRIPTION
 
Watch-Command function executes a command or expression waits for a number of seconds and then runs it again.
 
.PARAMETER Seconds
The number of seconds between the concecutive runs. The default value is 5.
 
.PARAMETER Command
The command to run
 
.EXAMPLE
Watch-Command -Command {Get-ChildItem C:\}
This command will display the contents of the c:\ folder every 5 seconds.
 
.EXAMPLE
Watch-Command -Command {Get-Content C:\file.log} -Seconds 10
This command will display the contents of the c:\file.log file every 10 seconds.
 
#>

    param
    (
        [Parameter(Mandatory=$True,Position=1)]
        [string]$Command,

        [Parameter(Mandatory=$False,Position=2)]
        [int]$Seconds = 5
    )

    Clear-Host
    Invoke-Expression $Command

    while ($true) {
        Clear-Host
        Invoke-Expression $Command
        Start-Sleep -Seconds $Seconds
    }
}
#endregion

#region New-MessageBox
Function New-MessageBox
{
    <#
    .SYNOPSIS
 
    Create a message box.
    .DESCRIPTION
 
    The New-MessageBox cmdlet will create a new message box.
 
    .PARAMETER Title
    The title of the message box.
 
    .PARAMETER Message
    The message of the message box.
 
    .PARAMETER Type
    The type of the message box.
 
    .PARAMETER Icon
    The icon of the message box.
 
    .PARAMETER SecondsToWait
    The number of seconds that the messagebox will be active.
 
    .NOTES
    Return values
        1: User selected OK
        2: User selected Cancel
    #>


    # https://msdn.microsoft.com/en-us/library/x83z1d9f(v=vs.84).aspx

    Param
    (
        [string]$Title,
        [string]$Message,
        [ValidateSet("OK", "OK-Cancel")]
        [string]$Type = "OK",
        [ValidateSet("Stop", "Exclamation", "Information")]
        [string]$Icon = "Information",
        [int]$SecondsToWait = 0
    )

    # Set the default variables
    $TypeCode = 0x0
    $IconTypeCode = "0x10"

    # Create the shell instance
    $wshell = New-Object -ComObject Wscript.Shell

    # Check the message box type
    if( $Type -eq "OK" )
    {
        $TypeCode = 0x0
    }
    if( $Type -eq "OK-Cancel" )
    {
        $TypeCode = 0x1
    }
    if( $Type -eq "Abort-Retry-Ignore" )
    {
        $TypeCode = 0x2
    }
    if( $Type -eq "Yes-No-Cancel" )
    {
        $TypeCode = 0x3
    }
    if( $Type -eq "Yes-No" )
    {
        $TypeCode = 0x4
    }
    if( $Type -eq "Retry-Cancel" )
    {
        $TypeCode = 0x5
    }
    if( $Type -eq "Cancel-TryAgain-Continue" )
    {
        $TypeCode = 0x6
    }

    # Check the message box icon
    if( $Icon -eq "Stop" )
    {
        $IconTypeCode = 0x10
    }
    if( $Icon -eq "Question" )
    {
        $IconTypeCode = 0x20
    }
    if( $Icon -eq "Exclamation" )
    {
        $IconTypeCode = 0x30
    }
    if( $Icon -eq "Information" )
    {
        $IconTypeCode = 0x40
    }

    # Display the message box
    $retval = $wshell.Popup($Message,0,$Title,$TypeCode)
# $wshell.Popup($Message,$SecondsToWait,$Title,$TypeCode)
    
    return $retval
}
#endregion

#region Set-UserDefinedVariables
Function Set-UserDefinedVariables
{
    <#
    .SYNOPSIS
 
    Set the current session variables as user defined.
    .DESCRIPTION
 
    The Set-UserDefinedVariables cmdlet will set the variables in the current session as user defined in order for Clear-UserDefinedVariables to not remove them.
 
    .NOTES
    This cmdlet uses the __UserDefinedVariables variable.
 
    #>

    try
    {
        $currentVariables = Get-Variable | % Name
        $currentVariables += "__UserDefinedVariables"
        New-Variable -Force -Name __UserDefinedVariables -Value $currentVariables -Scope Global -ErrorAction Stop
    }
    catch
    {
        Write-Error "Failed to set the variables!"
    }
}
#endregion

#region Get-UseDefinedVariables
Function Get-UserDefinedVariables
{
    <#
    .SYNOPSIS
 
    Get the current user defined variables.
    .DESCRIPTION
 
    The Get-UserDefinedVariables cmdlet will return all the variables that have been set as user defined by Set-UserDefinedVariables.
 
    .NOTES
    This cmdlet uses the __UserDefinedVariables variable.
 
    #>


    try
    {
        Get-Variable -Name "__UserDefinedVariables" -Scope Global -ErrorAction Stop |
            Out-Null
    }
    catch
    {
        Write-Error "User Defined Variables have not been set."
        return
    }

    Get-Variable |
        Where-Object {$_.Name -notin $__UserDefinedVariables}
}
#endregion

#region Remove-UserDefinedVariables
Function Remove-UserDefinedVariables
{
    <#
    .SYNOPSIS
 
    Remove variables that have not been set.
    .DESCRIPTION
 
    The Remove-UserDefinedVariables will remove all variables in current session that have not been set with Set-UserDefinedVariables.
 
    .NOTES
    This cmdlet uses the __UserDefinedVariables variable.
 
    #>


    [cmdletBinding(SupportsShouldProcess=$true, 
                   ConfirmImpact='Medium')]

    Param()

    try
    {
        Get-Variable -Name "__UserDefinedVariables" -Scope Global -ErrorAction Stop |
            Out-Null
    }
    catch
    {
        Write-Error "User Defined Variables have not been set."
        return
    }

    if ($pscmdlet.ShouldProcess("Clear-UserDefinedVariables"))
    {
        Get-Variable -Scope Global |
            % {
                if($__UserDefinedVariables -notcontains $_.Name)
                {
                    Write-Verbose ("Removing variable: " + $_.Name)
                    Remove-Variable $_.Name -Scope Global -Force
                }
            }
    }
}
#endregion

#region Clear-UserDefinedVariables
Function Clear-UserDefinedVariables
{
    <#
    .SYNOPSIS
 
    Clear variables that have not been set.
    .DESCRIPTION
 
    The Clear-UserDefinedVariables will clear all variables in current session that have not been set with Set-UserDefinedVariables.
 
    .NOTES
    This cmdlet uses the __UserDefinedVariables variable.
 
    #>


    [cmdletBinding(SupportsShouldProcess=$true, 
                   ConfirmImpact='Medium')]

    Param()

    try
    {
        Get-Variable -Name "__UserDefinedVariables" -Scope Global -ErrorAction Stop |
            Out-Null
    }
    catch
    {
        Write-Error "User Defined Variables have not been set."
        return
    }

    if ($pscmdlet.ShouldProcess("Clear-UserDefinedVariables"))
    {
        Get-Variable -Scope Global |
            % {
                if($__UserDefinedVariables -notcontains $_.Name)
                {
                    Write-Verbose ("Clearing variable: " + $_.Name)
                    Clear-Variable $_.Name -Scope Global -Force
                }
            }
    }
}
#endregion

#region Get-BootTime
Function Get-BootTime
{
<#
    .SYNOPSIS
     Displays the last time a system booted.
    .DESCRIPTION
     This function displays the last time a system booted.
    .EXAMPLE
     Get-BootTime
     This command displays the time when the local system last booted.
    .EXAMPLE
     Get-UpTime -ComputerName server1, server2
     This command displays last time that the systems server1 and server2 booted.
#>


     Param
     (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position=0)]
        [string[]] $ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $false, Position=1)]
        [system.management.automation.psCredential]$Credential
     )

    Begin{}

    Process
    {
        foreach( $c in $ComputerName)
        {
                if($Credential)
                {
                    Get-WmiObject win32_operatingsystem -Credential $Credential -ComputerName $c |
                        Select-Object @{Name="ComputerName"; Expression={$_.csname}}, @{Name=’BootTime’; Expression={$_.ConverttoDateTime($_.lastbootuptime)}}
                }
                else
                {
                    Get-WmiObject win32_operatingsystem -ComputerName $c |
                        Select-Object @{Name="ComputerName"; Expression={$_.csname}}, @{Name=’BootTime’; Expression={$_.ConverttoDateTime($_.lastbootuptime)}}
                }
        }

    }

    End{}
}
#endregion

#region Get-ActivationStatus
function Get-ActivationStatus
{
    <#
    .SYNOPSIS
        Get the activation status for a computer.
 
    .DESCRIPTION
        The Get-ActivationStatus function will get the windows activation status for a computer.
 
    .PARAMETER ComputerName
        The name of the computer. If not specified, the name of the local computer is used.
 
    .EXAMPLE
        Get-ActivationStatus
 
        This command will get the activation status of the local computer
 
    .EXAMPLE
        Get-ActivationStatus -ComputerName Server1
 
        This command will get the activation status of Server1 computer
 
    .INPUTS
        ComputerName: String
        Name: String
 
    #>


    [CmdletBinding()]
    
    param
    (
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]$ComputerName = $Env:COMPUTERNAME
    )

    Begin {}

    Process
    {
        foreach($c in $ComputerName)
        {
            try
            {
                $wpa = Get-WmiObject SoftwareLicensingProduct -ComputerName $c -Filter "ApplicationID = '55c92734-d682-4d71-983e-d6ec3f16059f'" -Property LicenseStatus -ErrorAction Stop
            }
            catch
            {
                $status = New-Object ComponentModel.Win32Exception ($_.Exception.ErrorCode)
                $wpa = $null
            }

            $out = New-Object psobject -Property @{
                ComputerName = $c;
                Status = [string]::Empty;
                }

            if($wpa)
            {
                :outer foreach($item in $wpa) {
                    switch ($item.LicenseStatus) {
                        0 {$out.Status = "Unlicensed"}
                        1 {$out.Status = "Licensed"; break outer}
                        2 {$out.Status = "Out-Of-Box Grace Period"; break outer}
                        3 {$out.Status = "Out-Of-Tolerance Grace Period"; break outer}
                        4 {$out.Status = "Non-Genuine Grace Period"; break outer}
                        5 {$out.Status = "Notification"; break outer}
                        6 {$out.Status = "Extended Grace"; break outer}
                        default {$out.Status = "Unknown value"}
                    }
                }

                Write-Output $out
            } 
            else
            {
                $out.Status = $status.Message
                Write-Error $out
            }
        }
    }
}
#endregion

#region Pause-Execution
Function Pause-Execution
{
    <#
    .SYNOPSIS
        Pause the execution until a key is pressed.
 
    .DESCRIPTION
        The Pause-Execution function will suspend the execution of a script until a key is pressed.
 
    .PARAMETER Message
        The message to display.
 
    .EXAMPLE
        Pause-Execution
 
    .EXAMPLE
        Pause-Execution -Message "Press any key to resume execution..."
 
    .NOTES
        Since the [Console]::Readkey is not implemented in Powershell ISE, when this function is called under ISE it will create a message box instead.
    #>


    Param
    (
        [string]$Message = "Press any key to continue... "
    )

    If ($psISE) {
        # The "ReadKey" functionality is not supported in Windows PowerShell ISE.
        $Shell = New-Object -ComObject "WScript.Shell"
        $Button = $Shell.Popup($Message, 0, "Execution Paused", 0)
 
        Return
    }
 
    Write-Host -NoNewline $Message
 
    $Ignore =
        16,  # Shift (left or right)
        17,  # Ctrl (left or right)
        18,  # Alt (left or right)
        20,  # Caps lock
        91,  # Windows key (left)
        92,  # Windows key (right)
        93,  # Menu key
        144, # Num lock
        145, # Scroll lock
        166, # Back
        167, # Forward
        168, # Refresh
        169, # Stop
        170, # Search
        171, # Favorites
        172, # Start/Home
        173, # Mute
        174, # Volume Down
        175, # Volume Up
        176, # Next Track
        177, # Previous Track
        178, # Stop Media
        179, # Play
        180, # Mail
        181, # Select Media
        182, # Application 1
        183  # Application 2
 
    While ($KeyInfo.VirtualKeyCode -Eq $Null -Or $Ignore -Contains $KeyInfo.VirtualKeyCode)
    {
        $KeyInfo = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown")
    }
 
    Write-Host
}
#endregion

#region Get-LoggedOnUser
Function Get-LoggedOnUser
{
    <#
     .SYNOPSIS
        Get the user sessions on a computer
 
    .DESCRIPTION
        The Get-LoggedOnUser will get all the user sessions from a computer including RDP sessions and disconnected sessions.
 
    .PARAMETER ComputerName
        The name of the computer to query.
 
    .EXAMPLE
        Get-LoggedOnUser -ComputerName server1
 
        This command will get the users connected to server named server1
    #>


    [cmdletBinding()]

    Param
    (
        [Parameter(Mandatory=$false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("Name")]
        [string[]]$ComputerName = $env:COMPUTERNAME
    )

    foreach($c in $ComputerName)
    {
        # query the computer for the sessions
        Write-Verbose "Getting sessions from computer $c..."
        $sessioninfo = query user /server:$c

        # parse sessions
        for($i=1; $i -lt $sessioninfo.Count; $i++)
        {
            # Split the output
            $columns = $sessioninfo[$i].Split(" ")

            # Remove empty lines
            $columns = $columns | Where-Object { $_.Trim().Length -gt 0}

            $tmp = 0
            if($columns.count -eq 8)  # there is no session name
            {
                $tmp = 1
            }

            # Create the custom object and populate the values
            $session = New-Object CPolydorou.General.LoggedOnUser
            $session.Username = $columns[0]

            $session.SessionID = $columns[$tmp + 1]

            if($columns.Count -eq 8)
            {
                $session.SessionName = $columns[1]
            }

            $session.SessionState = $columns[$tmp + 2]

            if($columns[$tmp + 3] -eq ".")
            {
                $session.SessionIdleTime = New-Object -TypeName System.TimeSpan -ArgumentList ("0","0","0")
            }
            else
            {
                $days = 0
                $hours = 0
                $minutes = 0

                if($columns[$tmp + 3] -like "*+*")
                {
                    $days = $columns[$tmp + 3].Split("+")[0]
                    $tmpvar = $columns[$tmp + 3].Split("+")[1]
                    $hours = $tmpvar.Split(":")[0]
                    $minutes = $tmpvar.Split(":")[1]
                }
                else
                {
                    $hours = $columns[$tmp + 3].Split(":")[0]
                    $minutes = $columns[$tmp + 3].Split(":")[1]
                }

                $session.SessionIdleTime = New-Object -TypeName System.TimeSpan -ArgumentList ($days, $hours, $minutes, 0)
            }

            $session.LogonTime = [DateTime]($columns[$tmp + 4] + " " + $columns[$tmp + 5] + " " + $columns[$tmp + 6])

            $session

        }

    }
}
#endregion

#region Disconnect-LoggedOnUser
Function Disconnect-LoggedOnUser
{
    <#
     .SYNOPSIS
        Disconnect a user sessions from a computer
 
    .DESCRIPTION
        The Disconnect-LoggedOnUser will get disconnect user session from a computer.
 
    .PARAMETER ComputerName
        The name of the computer to query.
 
    .PARAMETER SessionID
        The ID of the session to disconnect.
 
    .EXAMPLE
        Disconnect-LoggedOnUser -ComputerName server1 -SessionID 1
 
        This command will get the users connected to server named server1
 
    .EXAMPLE
        Get-LoggedOnUser |
            Where-Object {$_.Username -eq "admin"} |
                Disconnect-LoggedOnUser
 
        This command will disconnect the session of the user admin on server server1
    #>


    [cmdletBinding()]

    Param
    (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        $SessionID,

        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        $ComputerName
    )

    # Get the session from the remote computer
    Write-Verbose "Getting sessions on computer $ComputerName..."
    $session = Get-LoggedOnUser -ComputerName $ComputerName | Where-Object {$_.SessionID -eq $SessionID}

    if($session)
    {
        Write-Verbose "Signing out session $SessionID on computer $ComputerName..."
        rwinsta $SessionID /server:$ComputerName
    }
    else
    {
        Write-Error "Could not find a session with identity $SessionID on computer $ComputerName."
    }
}
#endregion

#region Send-Email
Function Send-Email
{
    <#
     .SYNOPSIS
        Send an email message.
 
    .DESCRIPTION
        The Send-Email message function will send an email message.
 
    .PARAMETER From
        The "From" address.
 
    .PARAMETER To
        The "To" addresses.
 
    .PARAMETER Subject
        The subject of the message.
 
    .PARAMETER Body
        The body of the message.
 
    .PARAMETER From
        The "From" address.
 
    .PARAMETER BodyFormat
        The format of the body.
 
    .PARAMETER Attachment
        The path to the files to be attached.
 
    .PARAMETER CC
        The "CC" addresses.
 
    .PARAMETER BCC
        The "BCC" addresses.
 
    .PARAMETER SMTPServer
        The SMTP server to use.
 
    .PARAMETER Port
        The port of the SMTP server.
 
    .PARAMETER UseSSL
        Enable SSL when connecting to the server.
 
    .EXAMPLE
        Send-Email -From "someone@somedomain.com" -To "someone@anotherdomain.com" -Subject "Test Message" -Body "This is a test message." -SMTPServer myserver.domain.com -Port 25
    #>


    [cmdletBinding()]

    Param
    (
        [Parameter(
            Mandatory = $true
        )]
        [string]$From,

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

        [Parameter(
            Mandatory = $false
        )]
        [string]$Subject,

        [Parameter(
            Mandatory = $false
        )]
        [string]$Body,

        [Parameter(
            Mandatory = $false
        )]
        [ValidateSet('HTML', 'PlainText')]
        [string]$BodyFormat,

        [Parameter(
            Mandatory = $false
        )]
        [string[]]$Attachement,

        [Parameter(
            Mandatory = $false
        )]
        [string[]]$CC,

        [Parameter(
            Mandatory = $false
        )]
        [string[]]$BCC,

        [Parameter(
            Mandatory = $false
        )]
        [string]$SMTPServer = $env:COMPUTERNAME,

        [Parameter(
            Mandatory = $false
        )]
        [string]$Port = 25,

        [Parameter(
            Mandatory = $false
        )]
        [switch]$UseSSL,

        [Parameter(
            Mandatory = $false
        )]
        [System.Management.Automation.PSCredential]$Credential
    )

    # Create the message
    Write-Verbose "Creating message object..."
    $message = new-object Net.Mail.MailMessage

    # Set the from address
    Write-Verbose "Setting the from field..."
    $message.From = $From

    # Add the "To" recipients
    foreach($t in $To)
    {
        Write-Verbose "Adding recipient $t..."
        $message.To.Add($t)
    }

    # Set the subject
    Write-Verbose "Setting the message subject..."
    $message.Subject = $Subject

    # Set the body
    if( $BodyFormat -eq "HTML")
    {
        Write-Verbose "Setting body format to HTML..."
        $message.IsBodyHtml = $true
    }

    # Set the body
    Write-Verbose "Setting the message body..."
    $message.Body = $Body

    # Add the attachements
    $attachmentObjects = @()
    foreach($a in $Attachement)
    {
        if( (Test-Path $a) -ne $true )
        {
            Write-Error "Could not locate attachement $a"
            return
        }
        else
        {
            $p = Resolve-Path $a
            $attachmentObjects += New-Object Net.Mail.Attachment($p);
        }        
    }
    foreach($a in $attachmentObjects)
    {
        Write-Verbose ("Adding attachment " + $a.Name)
        $message.Attachments.Add($a);
    }

    # Create the smpt client
    Write-Verbose "Creating SMTP Client..."
    $smtpClient = new-object Net.Mail.SmtpClient($SMTPServer, $Port);

    # Set the SSL settings
    if($UseSSL)
    {
        Write-Verbose "Enabling SSL..."
        $smtpClient.EnableSSL = $true
    }

    if($Credential)
    {
        Write-Verbose "Using credentials..."
        $smtpClient.Credentials = New-Object System.Net.NetworkCredential($Credential.GetNetworkCredential().Username, $Credential.GetNetworkCredential().Password);
    }

    try
    {
        $smtpClient.send($message);
        Write-Verbose "Message sent."
    }
    catch
    {
        Write-Verbose "Failed to send message."
        Write-Error $Error[0].Exception
    }

    # Dispose the attachements
    foreach($a in $attachmentObjects)
    {
        Write-Verbose ("Disposing attachement " + $a.name)
        $a.Dispose();
    }
}
#endregion

#region Invoke-Script
Function Invoke-Script
{
    <#
    .SYNOPSIS
 
    Execute a local script on a remote computer.
    .DESCRIPTION
 
    This function will execute a script saved on the local computer to a remote computer.
    .PARAMETER computername
 
    Here, the dotted keyword is followed by a single parameter name. Don't precede that with a hyphen. The following lines describe the purpose of the parameter:
    .PARAMETER Computername
 
    The name of the remote computer.
    .EXAMPLE
 
    Invoke-Script -Computername Server1 -Path .\Test.ps1
 
    This command will execute the script Test.ps1 from the currently working directory on the server Server1
 
    .EXAMPLE
 
    $Servers = @("Server1","Server2")
    $firstScriptParameter = @("10.0.0.1","10.0.0.2")
    $secondScriptParameter = "192.168.0.1"
    $parameters = @($firstScriptParameter, $secondScriptParameter)
    Invoke-Script -Computername $Servers -Path .\Test.ps1 -Parameters $parameters
 
    This command will execute the script Test.ps1 from the currently working directory on the servers Server1 and Server2.
    Two parameters were passed to the script. The first one is an array of strings and the second one a single string.
    This is how you can adopt the parameters on the script to the parameters needed on your script.
 
    #>

    [cmdletBinding()]

    Param
    (
        [Parameter(Position=0, Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [Alias("Server")]
        [string[]]$Computername,

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

        [Parameter(Position=2, Mandatory=$false)]
        [array]$Parameters
    )

    Begin
    {
        # Test path
        if( (Test-Path $Path) -ne $true)
        {
            Throw "Could not open the script file."
        }
    }

    Process
    {
        # Create a script block using the contents of the file
        Write-Verbose "Reading script file."
        $script = Get-Content $Path -Raw
        $sb = [Scriptblock]::Create($script) 

        # Process each server in the Computername array
        Foreach($s in $Computername)
        {
            # Invoke the command on the remote server
            Write-Verbose ("Invoking script on computer " + $s)
            Invoke-Command -ComputerName $s -ScriptBlock $sb -ArgumentList ($Parameters)
        }
    }

    End {}
}
#endregion

#region Get-NETFrameworkVersion
function Get-NETFrameworkVersion
{
    <#
    .Synopsis
       List the installed .NET Framework versions.
    .DESCRIPTION
       List the installed .NET Framework versions.
    .NOTES
    https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed
    #>


    [CmdletBinding()]

    Param
    (
        # The name of the computer to check
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        $ComputerName
    )

    Begin
    {
        #region Define the version table
        $Versions = @{
                        378389 = ".NET Framework 4.5"
                        378675 = ".NET Framework 4.5.1 installed with Windows 8.1"  
                        378758 = ".NET Framework 4.5.1 installed on Windows 8, Windows 7 SP1, or Windows Vista SP2"
                        379893 = ".NET Framework 4.5.2"
                        393295 = ".NET Framework 4.6 installed with Windows 10" 
                        393297 = ".NET Framework 4.6 installed on all other Windows OS versions other than 10"
                        394254 = ".NET Framework 4.6.1 installed on Windows 10"
                        394271 = ".NET Framework 4.6.1 installed on all other Windows OS versions other than 10"
                        394802 = ".NET Framework 4.6.2 installed on Windows 10 Anniversary Update"
                        394806 = ".NET Framework 4.6.2 installed on all other Windows OS versions other than 10 Anniversary Update"
                        460798 = ".NET Framework 4.7 installed on Windows 10 Creators Update"
                        460805 = ".NET Framework 4.7 installed on all other Windows OS versions other than 10 Creators Update"
                        461308 = ".NET Framework 4.7.1 installed on Windows 10 Creators Update"
                        461310 = ".NET Framework 4.7.1 installed on all other Windows OS versions other than 10 Creators Update"
                        461808 = ".NET Framework 4.7.2 installed on Windows 10 April 2018 Update"
                        461814 = ".NET Framework 4.7.2 installed on all other Windows OS versions other than Windows 10 April 2018 Update"
                      }
        #endregion
    }

    Process
    {
        if($ComputerName)
        {
            # Use remote registry and connect to the remote computers
            foreach($c in $ComputerName)
            {
                #region Open Remote Registry
                try
                {
                    $Registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $c)
                }
                catch
                {
                    Throw $_
                }
                $RootKey = $Registry.OpenSubKey("SOFTWARE\\Microsoft\\Net Framework Setup\\NDP")
                #endregion

                #region Process the keys
                $keys = $RootKey.GetSubKeyNames() |
                            Where-Object {$_ -like "v*"}

                # Loop through the keys
                foreach($k in $keys)
                {
                    # Check for the "Version" property of older .Net versions (prior to 4)
                    $versionKey = $RootKey.OpenSubKey($k)
        
                    if($k -like "v4*")
                    {
                        # Handle v4 setup
                        foreach($tk in $versionKey.GetSubKeyNames())
                        {
                            $typeSubKey = $versionKey.OpenSubKey($tk)
                            $version = $typeSubKey.GetValue("Version")
                            $release = $typeSubKey.GetValue("Release")
                            $type = $tk
                            if($release -ne $null)
                            {
                                $descriptionKey = $Versions.Keys | Where-Object {$_ -eq $release}
                                $description = $Versions.$descriptionKey
                            }
                            else
                            {
                                $release = "N/A"
                                $description = "N/A"
                            }

                            $props = [ordered]@{
                                            Version = $version
                                            Release = $release
                                            Type = $type
                                            Description = $description
                                          }

                            New-Object -TypeName PSObject -Property $props
                        }

                    }
                    else
                    {
                        # Handle older .Net versioning
                        try
                        {
                            $version = $versionKey.GetValue("Version")
                            $release = "N/A"
                            $type = "N/A"
                            $description = "N/A"

                            $props = [ordered]@{
                                            Version = $version
                                            Release = $release
                                            Type = $type
                                            Description = $description
                                          }

                            New-Object -TypeName PSObject -Property $props
                        }
                        catch
                        {
                        }    
                    }
                }
            }        
        }
        else
        {
            # Use the local PowerShell registry provider
            $keys = Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' |
                        Where-Object {$_.PSChildName -like "v*"}

            # Loop through the keys
            foreach($k in $keys)
            {
                # Examine the subkeys
                if($k.PSChildName -like "v4*")
                {
                    $subKeys = Get-ChildItem -Path $k.PSPath
                    foreach($sk in $subKeys)
                    {
                        $type = $sk.PSChildName

                        try
                        {
                            $version = (Get-ItemProperty -Path $sk.PSPath -Name Version -ErrorAction Stop).Version
                        }
                        catch
                        {
                            $version = "N/A"
                        }

                        try
                        {
                            $release = (Get-ItemProperty -Path $sk.PSPath -Name Release -ErrorAction Stop).Release

                            $description = $Versions.$release
                        }
                        catch
                        {
                            $release = "N/A"
                            $description = "N/A"
                        }

                        $props = [ordered]@{
                                            Version = $version
                                            Release = $release
                                            Type = $type
                                            Description = $description
                                          }

                        New-Object -TypeName PSObject -Property $props
    
                    }
                }
                else
                {
                    try
                    {
                        $version = (Get-ItemProperty -Path $k.PSPath -Name Version -ErrorAction Stop).Version
                    }
                    catch
                    {}

                    try
                    {
                        $release = (Get-ItemProperty -Path $k.PSPath -Name Release -ErrorAction Stop).Release
                    }
                    catch
                    {
                        $release = "N/A"
                        $description = "N/A"
                    }

                    $props = [ordered]@{
                                        Version = $version
                                        Release = $release
                                        Type = "N/A"
                                        Description = $description
                                       }

                    New-Object -TypeName PSObject -Property $props
                }
            }        

        }
    }

    End {}
}
#endregion
#endregion

#region Remove-Duplicate
function Remove-Duplicate
{
    <#
    .Synopsis
       Remove duplicate objects from collections
    .DESCRIPTION
       Remove duplicate objects from collections by comparing the values of all or a subset of their properties
    .EXAMPLE
       Remove the duplicate objects in the "data" variable by comparing all their properties.
 
       PS C:\> $data
 
        Property1 Property2 Property3
        --------- --------- ---------
        a a b
        1 2 3
        1 1 1
        1 1 1
        1 a b
 
        PS C:\> $data | Remove-Duplicate
 
        Property1 Property2 Property3
        --------- --------- ---------
        a a b
        1 2 3
        1 1 1
        1 a b
 
    .EXAMPLE
       Remove the duplicate objects in the "data" variable by comparing the "Property1" property.
        PS C:\> $data
 
        Property1 Property2 Property3
        --------- --------- ---------
        a a b
        1 2 3
        1 1 1
        1 1 1
        1 a b
 
 
        PS C:\> $data | Remove-Duplicate -Property Property1
 
        Property1 Property2 Property3
        --------- --------- ---------
        a a b
        1 2 3
 
    .EXAMPLE
       Remove the duplicate objects in the "data" variable by comparing the "Property2" and "Property3" properties.
 
        PS C:\> $data
 
        Property1 Property2 Property3
        --------- --------- ---------
        a a b
        1 2 3
        1 1 1
        1 1 1
        1 a b
 
 
        PS C:\> $data | Remove-Duplicate -Property Property2,Property3
 
        Property1 Property2 Property3
        --------- --------- ---------
        a a b
        1 2 3
        1 1 1
 
 
    .EXAMPLE
       Remove the duplicate objects in the "data" variable without using the pipeline.
 
        PS C:\> $data
 
        Property1 Property2 Property3
        --------- --------- ---------
        a a b
        1 2 3
        1 1 1
        1 1 1
        1 a b
 
 
        PS C:\> Remove-Duplicate -InputObject $data
 
        Property1 Property2 Property3
        --------- --------- ---------
        a a b
        1 2 3
        1 1 1
        1 a b
 
    #>


    [CmdletBinding(PositionalBinding=$false)]

    Param
    (
        # The input objects
        [Parameter(Mandatory=$false, 
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   Position=0)]
        $InputObject,

        # The properties to compare
        [Parameter(Mandatory=$false,
                   Position=1)]
        [string[]]
        $Property
    )

    #region Begin
    Begin
    {
        # Create the array to hold the objects already
        # sent to the output stream
        $output = New-Object -TypeName System.Collections.ArrayList
    }
    #endregion

    #region Process
    Process
    {
        #region Process the input
        foreach($i in $InputObject)
        {
            $alreadySent = 0

            #region Properties have been specified
            if($Property)
            {
                #region Create the filter script
                $sb = ""
                foreach($p in $Property)
                {
                    if($sb.Length -ne 0)
                    {
                        $sb += " -and "
                    }

                    $sb += '$_.' + "'" + $p + "'" + ' -eq ' + '$i.' + $p
                }
                $filterScript = [scriptblock]::Create($sb)
                #endregion

                #region Apply the filter to get objects already sent to output
                $result = @($output |
                                Where-Object -FilterScript $filterScript )

                if($result.Count -gt 0)
                {
                    $alreadySent++
                } 
                #endregion
            }
            #endregion

            #region Comparing all properties
            else
            {
                #region Check if the object has been sent to the output stream already
                # Compare the object with all the objects in the collection
                foreach($o in $output)
                {
                    if(Compare-ObjectByProperty -ReferenceObject $i -DifferenceObject $o)
                    {
                        $alreadySent++
                        break
                    }
                }
                #endregion
            }
            #endregion

            #region If the object has not been sent, save it and send it
            if($alreadySent -eq 0)
            {
                $output.Add($i) |
                    Out-Null

                $i
            }
            #endregion

        }
        #endregion
    }
    #endregion

    #region End
    End
    {
    }
    #endregion
}
#endregion

#region Compare-ObjectByProperty
function Compare-ObjectByProperty 
{
    <#
    .Synopsis
       Compare two objects using their properties.
    .DESCRIPTION
       Test if two objects are identical using the values of their properties.
    .EXAMPLE
 
    PS C:\> $data
    The objects 2 and 3 of the below array are identical, yet the -eq operator returns $false.
    The Compare-ObjectByProperty function will compare the values of the properties of the objects
    instead of comparing the entire objects.
 
    Property1 Property2 Property3
    --------- --------- ---------
    a a b
    1 2 3
    1 1 1
    1 1 1
    1 a b
 
 
 
    PS C:\> $data[2] -eq $data[3]
    False
 
    PS C:\> Compare-ObjectByProperty -ReferenceObject $data[2] -DifferenceObject $data[3]
    True
 
    #>


    [cmdletBinding()]

    param(
        [Parameter(Mandatory=$true)]
        [PSCustomObject]
        $ReferenceObject,
        
        [Parameter(Mandatory=$true)]
        [PSCustomObject]
        $DifferenceObject
    )

    -not (Compare-Object $ReferenceObject.PSObject.Properties $DifferenceObject.PSObject.Properties)
}
#endregion

#region Get-PendingReboot
Function Get-PendingReboot 
{ 
    #region Parameters
    [CmdletBinding()] 

    Param( 
      [Parameter(Mandatory = $false,
                 Position=0,
                 ValueFromPipeline=$true,
                 ValueFromPipelineByPropertyName=$true
      )] 
      [Alias("Server", "Name")] 
      [string[]]
      $ComputerName = $env:COMPUTERNAME
    )
    #endregion
 
    #region Begin
    Begin {}
    #endregion

    #region Process
    Process
    { 
        Foreach ($c in $ComputerName)
        {
            Write-Verbose "Processing computer $c..."
             
            #region Initialize variables
            $PendingComputerRename = $false       # There is a pending computer rename
            $PendingFileRename = $false           # There are pending file rename operations
            $PendingFileRenameOperations = $null  # The pending file rename operations
            $PendingWindowsUpdate = $false        # There are pending Windows Update operations
            $PendingSCCM = $false                 # System Center Configuration Manager client is installed
            $PendingCBS = $false                  # Check if there is a pending reboot from Component Based Servicing
            #endregion

            #region Connect to the registry provider
            Write-Verbose "Connecting to the registry WMI provider..."
            try
            {
                $WMIRegistry = [WMIClass] "\\$c\root\default:StdRegProv"
                $HKLM = [UInt32] "0x80000002" 
            }
            catch
            {
                Write-Error "Could not connect to the registry WMI provider of $c."
                return
            }
            #endregion

            #region Check for pending computer rename operation
            Write-Verbose "Checking for pending computer rename..."
            try
            {
                # Get the active computer name
                $ActiveComputerName = $WMIRegistry.GetStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\","ComputerName").sValue

                # Get the computer name
                $ComputerName = $WMIRegistry.GetStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\","ComputerName").sValue
                
                # Check if a rename is pending
                if($ActiveComputerName -ne $ComputerName)
                {
                    $PendingComputerRename = $true
                }      
            }
            catch
            {
                Write-Error "Could not connect to the registy of $c. $_"
                return
            }
            #endregion

            #region Windows Update
            Write-Verbose "Checking for pending Windows Update reboot..."
            try
            {
                # Get the registry key
                $WURegistryKey = $WMIRegistry.EnumKey($HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\") 
                
                # Check if windows update requires restart
                $PendingWindowsUpdate = $WURegistryKey.sNames -contains "RebootRequired" 
            }
            catch
            {
                Write-Error "Could not connect to the registry of $c. $_"
                return
            }
            #endregion

            #region Check Pending File Rename
            Write-Verbose "Checking for pending file rename operations..."
            try
            {
                # Get the registry key
                $PendingFileRenameKey = $WMIRegistry.GetMultiStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\Session Manager\","PendingFileRenameOperations") 
                
                # Check if operations are pending
                if($PendingFileRenameKey.sValue)
                {
                    $PendingFileRename = $true

                    $PendingFileRenameOperations = $PendingFileRenameKey.sValue | Where-Object {$_.Length -gt 0} | Select-Object -Unique
                }
            }
            catch
            {
                Write-Error "Could not connect to the registry of $c. $_"
                return
            }
            #endregion

            #region Check for Component Based Servicing
            Write-Verbose "Checking for Component Based Servicing reboot..."
            try
            {
                # Get the OS version
                $WMI_OS = Get-WmiObject -Class Win32_OperatingSystem -Property BuildNumber, CSName -ComputerName $c -ErrorAction Stop 
              
                if ([Int32]$WMI_OS.BuildNumber -ge 6001)
                { 
                    $CBSKey = $WMIRegistry.EnumKey($HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\") 
                    $PendingCBS = $CBSKey.sNames -contains "RebootPending"
                }
            }
            catch
            {
                Write-Error "Could not connect to the registry of $c. $_"
                return
            }
            #endregion

            #region Check for SCCM
            Write-Verbose "Checking for SCCM client reboot..."
            try
            {
                # Connect to the WMI namespace
                $SCCMSDK = [wmiclass]"\\$c\root\ccm\ClientSDK:CCM_ClientUtilities"
                
                $PendingSCCM = ($reboot.DetermineIfRebootPending()).RebootPending
            }
            catch
            {
                # Check if the service exists
                try
                {
                    $WMIService = Get-WmiObject win32_service -Property Name, State -ComputerName $c -ErrorAction Stop | Where-Object {$_.Name -eq "CcmExec"}

                    if($WMIService)
                    {
                        # The service exists, check its state
                        if($WMIService.State -ne "Running")
                        {
                            Write-Warning "The SCCM service is not running."
                        }
                    }
                }
                catch
                {
                    # The service does not exist, do nothing
                }               
            }
            #endregion

            #region Create a custom object
            $properties = [ordered]@{
                            ComputerName = $c
                            PendingReboot = ($PendingComputerRename -or $PendingFileRename -or $PendingWindowsUpdate -or $PendingSCCM -or $PendingCBS)
                            PendingComputerRename = $PendingComputerRename
                            PendingWindowsUpdate = $PendingWindowsUpdate
                            PendingSCCM = $PendingSCCM
                            PendingCBS = $PendingCBS
                            PendingFileRename = $PendingFileRename
                            PendingFileRenameOperations = $PendingFileRenameOperations
                          }

            New-Object -TypeName PSObject -Property $properties            
            #endregion
        }
    }
    #endregion
 
    #region End
    End {} 
    #endregion
}
#endregion

#region Display-DirectoryTree
function Display-DirectoryTree
{
    #region Parameters
    [cmdletBinding()]

    Param
    (
        [Parameter(
            Mandatory = $false
        )]
        [string]
        $Path = (Get-Location).Path,

        [Parameter(
            Mandatory = $false
        )]
        [switch]
        $IncludeType,

        [Parameter(
            Mandatory = $false
        )]
        [string]
        $Indentation = "`t",

        [Parameter(
            Mandatory = $false
        )]
        [int]
        $MaxDepth = [int]::MaxValue
    )
    #endregion

    #region Begin
    Begin
    {
        #region Recursive function
        Function _RecursiveDisplayTree
        {
            Param
            (
                $RecursivePath,
                $Depth,
                $IncludeItemType,
                $IndentationString,
                $MaximumDepth
            )

            # Check the current depth
            if($Depth -ge $MaximumDepth)
            {
                Write-Verbose "Maximum depth $MaximumDepth was reached."
                return
            }

            # Get the files
            Get-ChildItem -Path $RecursivePath -File -Force |
                %{
                    if($IncludeItemType -eq $true)
                    {
                        ($IndentationString * $Depth) + "[F] " + $_.Name
                    }
                    else
                    {
                        ($IndentationString * $Depth) + $_.Name
                    }
                }

            # Process directories
            Get-ChildItem -Path $RecursivePath -Directory -Force |
                %{
                    if($IncludeItemType -eq $true)
                    {
                        ($IndentationString * $Depth) + "[D] " + $_.Name
                    }
                    else
                    {
                        ($IndentationString * $Depth) + $_.Name
                    }

                    _RecursiveDisplayTree -RecursivePath $_.FullName `
                                          -Depth ($Depth + 1) `
                                          -IncludeItemType $IncludeItemType `
                                          -IndentationString $IndentationString `
                                          -MaximumDepth $MaximumDepth
                }
        }
        #endregion
    }
    #endregion

    #region Process
    Process
    {
        if( (Test-Path -Path $Path) -ne $true)
        {
            Write-Error "Could not find folder $Path"
        }
        else
        {
            $fullPath = Resolve-Path -Path $Path

            _RecursiveDisplayTree -RecursivePath $fullPath.Path `
                                  -Depth 0 `
                                  -IncludeItemType ($MyInvocation.BoundParameters["IncludeType"].IsPresent -eq $true) `
                                  -IndentationString $Indentation `
                                  -MaximumDepth $MaxDepth
        }
    }
    #endregion

    #region End
    End {}
    #endregion
}
#endregion

#region Send-SyslogMessage
function Send-SyslogMessage
{
    #region Parameters
    [CmdletBinding(PositionalBinding=$false,                  
                  ConfirmImpact='Medium')]
    [Alias()]
    [OutputType([String])]

    Param
    (
        # The message to send
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   ValueFromRemainingArguments=$false)]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Message,

        # The syslog server hostname/IP
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Server,

        # The severity of the event
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet("Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Information", "Debug")]
        [String]
        $Severity,

        # The facility of the event
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet("Kern", "User", "Mail", "Daemon", "Auth", "Syslog", "LPR",
                     "News", "UUCP", "Cron", "AuthPriv", "FTP", "NTP", "Security",
                     "Console", "Solaris-Chron", "Local0", "Local1", "Local2",
                     "Local3", "Local4", "Local5", "Local6", "Local7")]
        [String]
        $Facility,

        # The host name
        [Parameter(Mandatory=$false)]
        [String]
        $Hostname = $env:COMPUTERNAME,

        # The application name
        [Parameter(Mandatory=$false)]
        [String]
        $Application = "PowerShell",

        # The protocol to use
        [Parameter(Mandatory=$false)]
        [ValidateSet("UDP", "TCP")]
        [string]
        $Protocol = "UDP",

        # The syslog server port
        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [int]
        $Port = 514
    )
    #endregion

    #region Begin
    Begin
    {
    }
    #endregion

    #region Process
    Process
    {
        # Process the facility
        [int]$FacilityInt = -1
        switch ($Facility)
        {
            'Kern'          {$FacilityInt = 0}
            'User'          {$FacilityInt = 1}
            'Mail'          {$FacilityInt = 2}
            'Daemon'        {$FacilityInt = 3}
            'Auth'          {$FacilityInt = 4}
            'Syslog'        {$FacilityInt = 5}
            'LPR'           {$FacilityInt = 6}
            'News'          {$FacilityInt = 7}
            'UUCP'          {$FacilityInt = 8}
            'Cron'          {$FacilityInt = 9}
            'AuthPriv'      {$FacilityInt = 10}
            'FTP'           {$FacilityInt = 11}
            'NTP'           {$FacilityInt = 12}
            'Security'      {$FacilityInt = 13} 
            'Console'       {$FacilityInt = 14}
            'Solaris-Chron' {$FacilityInt = 15}
            'Local0'        {$FacilityInt = 16}
            'Local1'        {$FacilityInt = 17}
            'Local2'        {$FacilityInt = 18}
            'Local3'        {$FacilityInt = 19}
            'Local4'        {$FacilityInt = 20}
            'Local5'        {$FacilityInt = 21}
            'Local6'        {$FacilityInt = 22}
            'Local7'        {$FacilityInt = 23} 
            Default         {}
        }

        # Process the severity
        [int]$SeverityInt = -1
        switch ($Severity)
        {
            'Emergency'   {$SeverityInt = 0}
            'Alert'       {$SeverityInt = 1}
            'Critical'    {$SeverityInt = 2}
            'Error'       {$SeverityInt = 3}
            'Warning'     {$SeverityInt = 4}
            'Notice'      {$SeverityInt = 5}
            'Information' {$SeverityInt = 6}
            'Debug'       {$SeverityInt = 7}
            Default     {}
        }

        # Calculate the priority of the message
        $Priority = ($FacilityInt * 8) + [int]$SeverityInt

        # Get the timestamp in a syslog format
        # Create a locale object
        $LocaleEN = New-Object System.Globalization.CultureInfo("en-US")
        #$Timestamp = Get-Date -Format "MMM dd HH:mm:ss"
# $Timestamp = (Get-Culture).TextInfo.ToTitleCase([DateTime]::Now.ToString('MMM dd HH:mm:ss', $LocaleEN))
# $Timestamp = (Get-Culture).TextInfo.ToTitleCase([DateTime]::Now.ToString('o', $LocaleEN))
        $Timestamp = (Get-Date).ToUniversalTime().ToString("o")

        foreach($m in $Message)
        {
            # Format the syslog message
            $syslogMessage = "<{0}>{1} {2} {3} {4}" -f $Priority, $Timestamp, $Hostname, $Application, $m
            Write-Verbose ("Sending message: " + $syslogMessage)

            # Create an encoding object to encode to ASCII
            $Encoder = [System.Text.Encoding]::ASCII

            # Convert the message to byte array
            try
            {
                Write-Verbose "Encoding the message."
                $syslogMessageBytes= $Encoder.GetBytes($syslogMessage)
            }
            catch
            {
                Write-Error "Failed to encode the message to ASCII."
                continue
            }

            # Send the Message
            if($Protocol -eq "UDP")
            {
                Write-Verbose "Sending using UDP."

                # Create the UDP Client object
                $UDPCLient = New-Object System.Net.Sockets.UdpClient
                $UDPCLient.Connect($Server, $Port)

                # Send the message
                try
                {
                    $UDPCLient.Send($syslogMessageBytes, $syslogMessageBytes.Length) |
                        Out-Null
                    Write-Verbose "Message sent."
                }
                catch
                {
                    Write-Error ("Failed to send the message. " + $_.Exception.Message)
                    continue
                }
            }
            else
            {
                Write-Verbose "Sending using TCP."

                # Send the message via TCP
                try
                {
                    # Create a TCP socket object
                    $socket = New-Object System.Net.Sockets.TcpClient($Server, $Port)

                    # Write the message in the stream
                    $stream = $socket.GetStream()
                    $stream.Write($syslogMessageBytes, 0, $syslogMessageBytes.Length)

                    # Flush and close the stream
                    $stream.Flush()
                    $stream.Close()

                    Write-Verbose "Message sent."
                }
                catch
                {
                    Write-Error ("Failed to send the message. " + $_.Exception.Message)
                    continue
                }
            }
        }
    }
    #endregion

    #region End
    End
    {
    }
    #endregion
}
#endregion

#endregion

#region Exports
Export-ModuleMember -Function ForEach-Object-Parallel
Export-ModuleMember -Function Get-ConsoleUser
Export-ModuleMember -Function Test-IsAdmin
Export-ModuleMember -Function Download-WinSCP
Export-ModuleMember -Function Get-PrinterLegacy
Export-ModuleMember -Function Get-PowerEvent
Export-ModuleMember -Function Get-StartupApplications
Export-ModuleMember -Function Get-UpTime
Export-ModuleMember -Function Get-PendingReboot
Export-ModuleMember -Function Get-RAMStatistics
Export-ModuleMember -Function Get-ComputerDetails
Export-ModuleMember -Function Get-SpecialFolder
Export-ModuleMember -Function Watch-Command
Export-ModuleMember -Function New-MessageBox
Export-ModuleMember -Function Set-UserDefinedVariables
Export-ModuleMember -Function Clear-UserDefinedVariables
Export-ModuleMember -Function Remove-UserDefinedVariables
Export-ModuleMember -Function Get-UserDefinedVariables
Export-ModuleMember -Function Get-BootTime
Export-ModuleMember -Function Get-ActivationStatus
Export-ModuleMember -Function Pause-Execution
Export-ModuleMember -Function Get-LoggedOnUser
Export-ModuleMember -Function Disconnect-LoggedOnUser
Export-ModuleMember -Function Send-Email
Export-ModuleMember -Function Invoke-Script
Export-ModuleMember -Function Get-NETFrameworkVersion
Export-ModuleMember -Function Compare-ObjectByProperty
Export-ModuleMember -Function Remove-Duplicate
Export-ModuleMember -Function Display-DirectoryTree
Export-ModuleMember -Function Send-SyslogMessage
#endregion