Public/system-tools.ps1

#region SystemInfo Getters
function Get-LocalDetails {
    <#
    .SYNOPSIS
    Retrieves local system details including hostname, username, uptime, domain, system manufacturer, OS install date, network information, default gateway, DNS servers, internet access, and domain access.
 
    .DESCRIPTION
    The Get-LocalDetails function retrieves various details about the local system and returns them as an object. The function collects information such as the hostname, username, uptime, domain, system manufacturer, OS install date, network information, default gateway, DNS servers, internet access, and domain access.
 
    .PARAMETER None
    This function does not accept any parameters.
 
    .EXAMPLE
    Get-LocalDetails
 
    This example demonstrates how to use the Get-LocalDetails function to retrieve local system details.
 
    .OUTPUTS
    The function returns an object with the following properties:
    - Hostname: The hostname of the local system.
    - Username: The username of the currently logged-in user.
    - UpTime: The uptime of the local system in days, hours, minutes, and seconds.
    - Domain: The domain of the local system.
    - SystemManufacturer: The manufacturer of the local system.
    - OsInstallDate: The installation date of the operating system.
    - netinfo: An array of network interface details, including interface name, IP address, subnet mask, prefix origin, and firewall zone.
    - defaultGateway: The IP address of the default gateway.
    - dnsServers: An array of DNS server IP addresses.
    - InternetAccess: Indicates whether the local system has internet access (True or False).
    - DomainAccess: Indicates whether the local system can access the specified domain (True or False).
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','')]
    param (
        # No parameters for this function
    )

    # Display information message
    Write-Information "Getting local details..." -InformationAction Continue

    # Initialize result object
    $result = "" | Select-Object Hostname, Username, UpTime, Domain, SystemManufacturer, OsInstallDate, netinfo, defaultGateway, dnsServers, InternetAccess, DomainAccess

    # Suppress progress output during execution
    $originalProgressPreference = $ProgressPreference
    $ProgressPreference = "SilentlyContinue"

    # Get system information using Get-ComputerInfo
    $systemInfo = Get-ComputerInfo

    # Restore original progress preference
    $ProgressPreference = $originalProgressPreference

    # Add hostname
    $result.Hostname = HOSTNAME.EXE

    # Add current user
    $result.Username = $systemInfo.CsUserName

    # Calculate system uptime
    $result.UpTime = (Get-Uptime).Readable

    # Add domain, system manufacturer, and OS install date
    $result.Domain = $systemInfo.CsDomain
    $result.SystemManufacturer = $systemInfo.CsManufacturer
    $result.OsInstallDate = $systemInfo.OsInstallDate

    # Collect network information
    $result.netinfo = Get-NetInfo

    # Get default gateway
    $result.defaultGateway = Get-NetInfo -defaultGateway

    # Get DNS server addresses
    $result.dnsServers = Get-NetInfo -dnsServers

    # Check internet access using custom function pingplus
    $result.InternetAccess = pingplus -target 8.8.8.8

    # Check domain access using custom function pingplus
    $result.DomainAccess = pingplus -target $result.Domain

    # Return the populated result object
    return $result
}

Function Get-DiskUsage {
    <#
    .SYNOPSIS
    Gets disk usage information for the local system.
 
    .DESCRIPTION
    This function retrieves disk usage information for the local system, including details such as drive letter, free space, total size, and used percentage.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWMICmdlet','')]
    param (
        # No parameters for this function
    )
    # Check Disk Space
    $diskSpace = Get-WmiObject Win32_LogicalDisk -ComputerName "localhost" | Where-Object { $_.DriveType -eq 3 } | Select-Object DeviceID, FreeSpace, Size, UsedPercentage
    $allDisks = @()
    foreach ($disk in $diskSpace) {
        $disk.FreeSpace = [math]::Round($disk.FreeSpace / 1GB, 2)
        $disk.Size = [math]::Round($disk.Size / 1GB, 2)
        $UsedPercentage = [math]::Round((($disk.Size - $disk.FreeSpace) / $disk.Size) * 100, 2)
        $allDisks += $disk

        if ($silent) { continue }
        $disk.UsedPercentage = ("[" + ("#" * $UsedPercentage) + " " * (100 - $UsedPercentage) + "]")
    }
    return $diskSpace
}

function Get-Apps {
    <#
    .SYNOPSIS
    Retrieves information about installed software on a Windows system.
 
    .DESCRIPTION
    This function queries the Windows Registry to gather information about installed software,
    focusing on the "Uninstall" key in the registry.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','')]
    param (
        # No parameters for this function
    )

    # Query the "Uninstall" key in the Windows Registry to get software information
    $queriedSoftware = Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall"

    # Array to store information about installed software
    $InstalledSoftware = @()

    # Loop through each object in the queried software
    foreach ($obj in $queriedSoftware) {
        # Check if DisplayName value is not null
        if ($null -eq $obj.GetValue('DisplayName')) { continue }

        # Create a custom object for each installed software
        $software = [PSCustomObject]@{
            DisplayName    = $obj.GetValue('DisplayName')
            DisplayVersion = $obj.GetValue('DisplayVersion')
            Publisher      = $obj.GetValue('Publisher')
            InstallDate    = Convert-DateFormat -InputDate $obj.GetValue('InstallDate')
            Size           = "$([math]::Round(($obj.GetValue('EstimatedSize')) / 1KB)) MB"
        }

        # Add the software object to the array
        $InstalledSoftware += $software
    }

    # Return the sorted and formatted table of installed software
    return ($InstalledSoftware | Sort-Object DisplayName | Format-Table)
}

function Trace-Eventlog {
    <#
    .SYNOPSIS
    Continuously monitors and retrieves new events from the specified event log with optional filtering.
 
    .DESCRIPTION
    This function continuously monitors and retrieves new events from the specified event log,
    allowing optional filtering by source, message, and entry type.
 
    .PARAMETER LogName
    Specifies the name of the event log to monitor. Default is "Application".
 
    .PARAMETER Source
    Specifies the event source. Default is "*".
 
    .PARAMETER Message
    Specifies a message filter for the events. Default is "*".
 
    .PARAMETER EntryType
    Specifies the entry types to include (e.g., "Error", "Warning", "Information", "SuccessAudit", "FailureAudit").
    #>

    param (
        [Parameter()]
        [ValidateSet("Application", "HardwareEvents", "System", "Security")]
        [string]$LogName = "Application",
        [Parameter()]
        [string]$Source = "*",
        [Parameter()]
        [string]$Message = "*",
        [Parameter()]
        [ValidateSet("Error", "Warning", "Information", "SuccessAudit", "FailureAudit")]
        [String]$EntryType
    )

    # Array to store retrieved events
    $allevents = @()

    # Continuous monitoring loop
    while ($true) {
        try {
            # Retrieve new events based on parameters
            if ($EntryType) {
                $newEvents = Get-EventLog $LogName -Newest 10 -Source $Source -Message "*$Message*" -EntryType $EntryType -ErrorAction Stop | Where-Object { $_.Index -notin $allevents.Index }
            } else {
                $newEvents = Get-EventLog $LogName -Newest 10 -Source $Source -Message "*$Message*" -ErrorAction Stop | Where-Object { $_.Index -notin $allevents.Index }
            }
        }
        catch {
            Write-Error "Error: $_"
            return;
        }

        # If new events are retrieved, add them to the array in reverse order
        if ($null -ne $newEvents) {
            [array]::Reverse($newEvents)
            $allevents += $newEvents
        }

        # Output the new events
        $newEvents

        # Pause for a short interval before checking for new events again
        [System.Threading.Thread]::Sleep(100)
    }
}
#endregion

#region SystemBoot getters
function Get-Uptime {
    <#
    .SYNOPSIS
    Retrieves the system uptime in days, hours, minutes, seconds, and a readable format.
 
    .DESCRIPTION
    This function calculates and returns the system uptime in various formats, including days, hours, minutes, seconds, and a readable string.
    #>

    param (
        # No parameters for this function
    )
    #TODO support remote targets
    # Create a new PSObject to store the result of system uptime
    $result = New-Object PSObject

    # Calculate the system uptime by subtracting LastBootUpTime from the current date
    $uptime = (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime

    # Add properties to the result object for days, hours, minutes, and seconds
    $result | Add-Member -MemberType NoteProperty -Name "Days" -Value $uptime.Days
    $result | Add-Member -MemberType NoteProperty -Name "Hours" -Value $uptime.Hours
    $result | Add-Member -MemberType NoteProperty -Name "Minutes" -Value $uptime.Minutes
    $result | Add-Member -MemberType NoteProperty -Name "Seconds" -Value $uptime.Seconds

    # Add a property for a human-readable format of the uptime
    $result | Add-Member -MemberType NoteProperty -Name "Readable" -Value "$($uptime.Days)d, $($uptime.Hours)h, $($uptime.Minutes)m, $($uptime.Seconds)s"

    # Return the result object
    return $result
}

function Get-BootHistory {
    <#
    .SYNOPSIS
    Retrieves information about system boot history.
 
    .DESCRIPTION
    This function retrieves information about system boot history by querying the System event log for event IDs 1074 and 6005.
    It provides details such as timestamp, user, reason/application for the boot, and the associated action.
    #>

    param (
        # No parameters for this function
    )

    # Array to store the result of boot history information
    $result = @()

    # Array to store power events (shutdown and startup) from the System log
    $powerEvents = @()
    $powerEvents += Get-WinEvent -FilterHashTable @{LogName='System'; ID=1074}
    $powerEvents += Get-WinEvent -FilterHashTable @{LogName='System'; ID=6005}

    # Sort the power events based on TimeCreated
    $powerEvents = $powerEvents | Sort-Object TimeCreated

    # Loop through each power event and create an output object
    foreach ($powerEvent in $powerEvents) {
        $output = "" | Select-Object TimeStamp, User, Reason, Action
        $output.TimeStamp = $powerEvent.TimeCreated

        # Define the regular expression pattern to capture the "on behalf of user" information
        $pattern = "on behalf of user ([^\s]+)"

        # Use the -match operator to find the match in the message property
        if ($powerEvent.message -match $pattern) {
            $onBehalfOfUser = $matches[1]

            # Check if the user is not "NT" (system)
            if ($onBehalfOfUser -ne "NT") {
                $output.User = $onBehalfOfUser
            } else {
                $output.User = "System"
            }
        } else {
            $output.User = "System"
        }

        # Determine the action based on the event ID
        if ($powerEvent.ID -eq 6005) {
            $output.Action = "Startup"
        }
        else {
            $output.Action = $powerEvent.Properties[4].Value
        }

        # For events other than startup, capture the reason property
        if ($powerEvent.ID -ne 6005) {
            $output.Reason = $powerEvent.Properties[0].Value
        }

        # Add the output object to the result array
        $result += $output
    }

    # Return the result array in a formatted table
    return ($result | Format-Table)
}
#endregion

#region File getters/setters
Function Find-FileFast {
    <#
    .SYNOPSIS
    Quickly searches for files in a directory and its subdirectories.
     
    .DESCRIPTION
    This function quickly searches for files in a directory and its subdirectories using a custom C# class.
     
    .PARAMETER Path
    Specifies the path to search for files. Default is the current directory.
     
    .PARAMETER FileName
    Specifies the name of the file to search for. Wildcards are supported.
     
    .EXAMPLE
    Find-FileFast -FileName "example.txt"
    #>

    [alias("ff", "Find-File", "Get-File")]
    param (
        [Parameter()]
        [string]$Path = ".\",
        [Parameter(Mandatory=$true, Position=0)]
        [string]$FileName
    )
    add-type -TypeDefinition @"
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
 
public class FileSearch {
    public struct WIN32_FIND_DATA {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }
 
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern IntPtr FindFirstFile
      (string lpFileName, out WIN32_FIND_DATA lpFindFileData);
 
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindNextFile
      (IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);
 
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindClose(IntPtr hFindFile);
 
    static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
 
    public static class Globals {
        public static BlockingCollection<string> resultFileList {get;set;}
    }
 
    public static BlockingCollection<string> GetTreeFiles(string path, string searchFile) {
        Globals.resultFileList = new BlockingCollection<string>();
        List<string> dirList = new List<string>();
        searchFile = @"^" + searchFile.Replace(@".",@"\.").Replace(@"*",@".*").Replace(@"?",@".") + @"$";
        GetFiles(path, searchFile);
        return Globals.resultFileList;
    }
 
    static void GetFiles(string path, string searchFile) {
        path = path.EndsWith(@"\") ? path : path + @"\";
        List<string> dirList = new List<string>();
        WIN32_FIND_DATA fileData;
        IntPtr handle = INVALID_HANDLE_VALUE;
        handle = FindFirstFile(path + @"*", out fileData);
        if (handle != INVALID_HANDLE_VALUE) {
            FindNextFile(handle, out fileData);
            while (FindNextFile(handle, out fileData)) {
                if ((fileData.dwFileAttributes & 0x10) > 0) {
                    string fullPath = path + fileData.cFileName;
                    dirList.Add(fullPath);
                } else {
                    if (Regex.IsMatch(fileData.cFileName, searchFile, RegexOptions.IgnoreCase)) {
                        string fullPath = path + fileData.cFileName;
                        Globals.resultFileList.TryAdd(fullPath);
                    }
                }
            }
            FindClose(handle);
            Parallel.ForEach(dirList, (dir) => {
                GetFiles(dir, searchFile);
            });
        }
    }
}
"@


    $searchDir = (Resolve-Path $Path).Path
    [fileSearch]::GetTreeFiles($searchDir, $FileName)

    # Props to https://stackoverflow.com/questions/63956318/fastest-way-to-find-a-full-path-of-a-given-file-via-powershell - Carsten
}

function Invoke-FileTouch {
    <#
    .SYNOPSIS
    Updates the last write time of a file to the current date and time.
     
    .DESCRIPTION
    This function updates the last write time of a file to the current date and time.
     
    .PARAMETER file
    Specifies the file to update the last write time for.
    Using "\" at the end of the file path will create a directory if it does not exist.
     
    .PARAMETER date
    Specifies the date and time to set as the last write time. Default is the current date and time.
     
    .EXAMPLE
    Invoke-FileTouch -file "C:\example.txt"
    touch "C:\example.txt" -date "2021-01-01"
    #>

    [alias("touch")]
    [Cmdletbinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory=$true, Position=0)]
        [string[]]$file,
        [Parameter()]
        [ValidateScript({$_ -as [datetime]})]
        [string]$date = (Get-Date),
        [Parameter()]
        [switch]$alsoUpdateCreationTime
    )
    process {
        # Set the error action preference to stop
        $ErrorActionPreference = "Stop"

        # Loop through each file and update the last write time
        foreach ($_ in $file) {
            # Check if the file exists
            if ($PSCmdlet.ShouldProcess("Localhost", "Create file/directory if file: '$($_)' does not exist, then update last write time")) {
                if (-not (Test-Path $_)) {
                    if ($_.EndsWith("\")) {
                        # If the file path ends with "\", create a directory
                        New-Item -Path $_ -ItemType Directory | Out-Null
                    } else {
                        # Otherwise create a new file
                        New-Item -Path $_ -ItemType File | Out-Null
                    }
                }
                # Set the last write time to the specified date
                (Get-Item $_).LastWriteTime = $date
                # If the -alsoUpdateCreationTime switch is used, update the creation time as well
                if ($alsoUpdateCreationTime) {
                    (Get-Item $_).CreationTime = $date
                }
            }
        }
    }
}
#endregion

function Get-CertificateExpiry {
    <#
    .SYNOPSIS
    Retrieves information about certificate expiry for the specified target.
 
    .DESCRIPTION
    This function retrieves information about certificate expiry for either the local machine or a remote target.
    It checks the certificates in the "My" store and provides details such as subject, thumbprint, expiration date, and whether it will expire soon.
 
    .PARAMETER target
    Specifies the target machine. If not provided, it checks certificates on the local machine.
 
    .PARAMETER credentials
    Specifies the credentials to be used when checking certificates on a remote machine.
    #>

    param (
        $target,
        [PSCredential]$credentials
    )
    #TODO Accept multiple targets
    # Array to store the result of certificate information
    $result = @()

    # Array to store all certificates
    $allCertificates = @()

    # Check if a target machine is specified
    if ($null -eq $target) {
        # If no target specified, check certificates on the local machine
        $allCertificates += Get-ChildItem -Path Cert:\LocalMachine\My
    }
    else {
        # If a target is specified and credentials are not provided, prompt for credentials
        if ($null -eq $credentials) {
            $credentials = Get-Credential -Message "Enter credentials for $target"
        }

        # Invoke-Command to retrieve certificates on a remote machine
        $allCertificates += Invoke-Command -ComputerName $target -Credential $credentials {
            Get-ChildItem -Path Cert:\LocalMachine\My
        }
    }

    # Loop through each certificate and create an output object
    foreach ($certificate in $allCertificates) {
        $output = "" | Select-Object Subject, Thumbprint, NotAfter, Soon
        $output.Subject = $certificate.Subject
        $output.Thumbprint = $certificate.Thumbprint
        $output.NotAfter = $certificate.NotAfter
        $output.Soon = ($certificate.NotAfter -lt (Get-Date).AddDays(60))  # Check if the certificate will expire soon (within 60 days)

        # Add the output object to the result array
        $result += $output
    }

    # Return the result array
    return $result
}