Public/SCJBServerTeamTools.ps1

function SCJBServerTeamTools {
    <#
    .LINK
        https://www.powershellgallery.com/packages/SCJBServerTeamTools
    .EXAMPLE
        SCJBServerTeamTools
        Open to the home window
    .EXAMPLE
        SCJBServerTeamTools -QuickLaunch ADLookups
        Open to the ADLookups window
    .EXAMPLE
        SCJBServerTeamTools -QuickLaunch RSOP
        Open to the RSOP window
    .EXAMPLE
        STT -QuickLaunch SCCMDeployments
        Open using the alias to the SCCM deployments window
    .EXAMPLE
        STT -ShowSettings
        Outputs the tools settings file in a PS object.
    #>


    # MAIN TODO
    # - function for creating windows (leave out variable creation) or a foreach of each .xml in the assets folder.
    # - help window
    # - users - member of sccm collection (probably doesn't matter, we have like 10 user collections)
    # - IntuneDeviceDetails needs to exit cleaner back to the home window when there is an error, such as failing to login to graph.
    # - _UpdateSCCMDevices use API instead
    # - SCCM Collection members
    # - datagrid header padding between columns
    # - hide first run output of LoganShell folder creation
    # - move 'constants' to this function.
    # - ComputerStats CPU round to 0 or 2 decimal places
    # - user tab - actions with a verify messagebox
    # - computer tab - install updates
    # - computer tab - schedule reboot
    # - verify that the settingssaved variable works correctly
    # - add user or computer to a group with out-gridview
    # - use the $Session variable for stuff
    # - options - default domain and add that to _SaveSettings
    # - adlookups - updating caches shows please wait while loading
    # - find tab - users search show name
    # - rsop - show text box to right for the text based gpresult that collapses
    # - datagrid - selected item use primary color
    # - smarter _SaveSettings so it doesn't overwrite existing credentials

    [CmdletBinding()]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Blackholes are fun.')]

    param (
        [Parameter(Mandatory = $false)]
        [ValidateSet('ADLookups', 'RSOP', 'SCCMDeployments', 'ComputerStats', 'Passphrase', 'WUG')]
        [string]
        $QuickLaunch,

        [Parameter(Mandatory = $false, HelpMessage = 'This will just output the contents of the settings file.')]
        [switch]
        $ShowSettings
    )

    # WPF framework
    Add-Type -AssemblyName PresentationFramework

    # Windows Form framework (message boxes)
    Add-Type -AssemblyName System.Windows.Forms
    # Add-Type -AssemblyName System.Drawing

    #region Pre-run checks and constants
    # Testing for the AD module manifest since workstations and servers use different methods to check the installation of RSAT tools and this seems faster.
    # TODO was still not working on SVM165 even with RSAT installed.
    # if (Test-Path 'C:\WINDOWS\System32\WindowsPowerShell\v1.0\Modules\ActiveDirectory\ActiveDirectory.psd1') {
    # Write-Warning 'RSAT AD Tools must be installed prior to use.'
    # break
    # }

    try {
        $Script:PDC = (Get-ADDomain).PDCEmulator
    }
    catch {
        Write-Warning 'Unable to contact a domain controller.'
        break
    }

    $Script:Module = Get-Module SCJBServerTeamTools
    $Script:ModuleName = 'SCJBServerTeamTools'

    #endregion

    #region First Run
    Write-Verbose 'Checking for first run'
    # TODO move this to it's own function with _InitialSettings
    if (!(Test-Path -Path $env:APPDATA\LoganShell\settings.json)) {
        # Settings file doesn't exist
        Write-Verbose 'First Run, creating directory and saving settings.'
        if (!(Test-Path -Path $env:APPDATA\LoganShell\)) {
            $Blackhole = New-Item -Path $env:APPDATA -Name 'LoganShell' -ItemType Directory
        }
        Write-Verbose 'Prompting for initial input'

        $userinput = Read-Host -Prompt 'Do you want to enable SCCM functionality? (Y/N)'
        switch ($userinput) {
            'Y' {
                $EnableSCCM = $true
                $SCCMServerInput = Read-Host -Prompt 'What is the hostname for the SCCM server?'
            }
            'N' {
                $EnableSCCM = $false
            }
            default {
                Write-Host 'Invalid option. Run again.'
                break
            }
        }

        $userinput = Read-Host -Prompt 'Do you want to enable Exchange on-premise functionality? (Y/N)'
        switch ($userinput) {
            'Y' {
                $EnableExchange = $true
            }
            'N' {
                $EnableExchange = $false
            }
            default {
                Write-Host 'Invalid option. Run again.'
                break
            }
        }

        Write-Verbose 'Setting temporary storage location'
        $ObjectFolder = 'C:\temp\Loganshell\'
        if (!(Test-Path $ObjectFolder)) {
            Write-Verbose 'Creating path.'
            New-Item -Path $ObjectFolder -ItemType Directory -Force -ErrorAction Stop
        }

        try {
            # Save default settings
            $InitialSplat = @{
                Path         = "$env:APPDATA\LoganShell\"
                SCCM         = $EnableSCCM
                SCCMServer   = $SCCMServerInput
                Exchange     = $EnableExchange
                DefaultDC    = $PDC
                ObjectFolder = $ObjectFolder
            }
            _InitialSettings @InitialSplat -ErrorAction Stop
        }
        catch {
            throw $_
        }
        Write-Verbose 'Done setting up for first run.'
    }
    #endregion

    #region Loading Settings
    Write-Verbose 'Loading Settings'
    try {
        _LoadSettings -File $env:APPDATA\LoganShell\Settings.json -ErrorAction Stop
    }
    catch {
        throw $_
    }

    if ($ShowSettings) {
        Write-Output ($STTSettings | ConvertTo-Json)
        break
    }

    if ($STTSettings.Theme) {
        switch ($STTSettings.Theme) {
            'Light' {
                $ThemeFile = "$ModuleRoot\Assets\LightTheme.xaml"
            }
            'Dark' {
                $ThemeFile = "$ModuleRoot\Assets\DarkTheme.xaml"
            }
            'Violet' {
                $ThemeFile = "$ModuleRoot\Assets\VioletTheme.xaml"
            }
            Default {
                $ThemeFile = "$ModuleRoot\Assets\LightTheme.xaml"
            }
        }
    }
    else {
        # Old settings file
        $ThemeFile = "$ModuleRoot\Assets\LightTheme.xaml"
    }
    #endregion

    Write-Verbose 'Starting to launch window'

    switch ($QuickLaunch) {
        'ADLookups' {
            #region ADLookups
            $xamlFile = "$ModuleRoot\Assets\ADLookups.xaml"

            #create window
            $inputXML = Get-Content $xamlFile -Raw
            $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
            [XML]$XAML = $inputXML
            $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

            #Read XAML
            $reader = (New-Object System.Xml.XmlNodeReader $xaml)
            try {
                $ADLookups = [Windows.Markup.XamlReader]::Load( $reader )
            }
            catch {
                Write-Warning $_.Exception
                throw
            }

            $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                try {
                    Set-Variable -Name "ADLookups_$($_.Name)" -Value $ADLookups.FindName($_.Name) -ErrorAction Stop
                }
                catch {
                    throw
                }
            }
            _ADLookupsUIEvents
            #endregion
            Write-Output 'Loading ADLookups....'
            $null = $ADLookups.ShowDialog()
        }
        'RSOP' {
            #region RSOP
            $xamlFile = "$ModuleRoot\Assets\RSOP.xaml"

            #create window
            $inputXML = Get-Content $xamlFile -Raw
            $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
            [XML]$XAML = $inputXML
            $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

            #Read XAML
            $reader = (New-Object System.Xml.XmlNodeReader $xaml)
            try {
                $RSOP = [Windows.Markup.XamlReader]::Load( $reader )
            }
            catch {
                Write-Warning $_.Exception
                throw
            }

            $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                try {
                    Set-Variable -Name "RSOP_$($_.Name)" -Value $RSOP.FindName($_.Name) -ErrorAction Stop
                }
                catch {
                    throw
                }
            }
            _RSOPWindowUIEvents
            #endregion
            Write-Output 'Loading RSOP....'
            $null = $RSOP.ShowDialog()
        }
        'SCCMDeployments' {
            #region SCCMDeployments
            $xamlFile = "$ModuleRoot\Assets\SCCMDeployments.xaml"

            #create window
            $inputXML = Get-Content $xamlFile -Raw
            $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
            [XML]$XAML = $inputXML
            $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

            #Read XAML
            $reader = (New-Object System.Xml.XmlNodeReader $xaml)
            try {
                $SCCMDeployments = [Windows.Markup.XamlReader]::Load( $reader )
            }
            catch {
                Write-Warning $_.Exception
                throw
            }

            $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                try {
                    Set-Variable -Name "SCCMDeployments_$($_.Name)" -Value $SCCMDeployments.FindName($_.Name) -ErrorAction Stop
                }
                catch {
                    throw
                }
            }
            _SCCMDeploymentsUIEvents
            #endregion
            Write-Output 'Loading SCCM Deployments....'
            $null = $SCCMDeployments.ShowDialog()
        }
        'ComputerStats' {
            #region ComputerStats
            $xamlFile = "$ModuleRoot\Assets\ComputerStats.xaml"

            #create window
            $inputXML = Get-Content $xamlFile -Raw
            $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
            [XML]$XAML = $inputXML
            $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

            #Read XAML
            $reader = (New-Object System.Xml.XmlNodeReader $xaml)
            try {
                $Global:ComputerStats = [Windows.Markup.XamlReader]::Load( $reader )
            }
            catch {
                Write-Warning $_.Exception
                throw
            }

            $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                try {
                    Set-Variable -Name "ComputerStats_$($_.Name)" -Value $ComputerStats.FindName($_.Name) -Scope Global -ErrorAction Stop
                }
                catch {
                    throw
                }
            }
            _ComputerStatsUIEvents
            #endregion
            Write-Output 'Loading Computer Stats....'
            $null = $ComputerStats.ShowDialog()
        }
        'Passphrase' {
            #region Passphrases
            $xamlFile = "$ModuleRoot\Assets\Passphrases.xaml"

            #create window
            $inputXML = Get-Content $xamlFile -Raw
            $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
            [XML]$XAML = $inputXML
            $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

            #Read XAML
            $reader = (New-Object System.Xml.XmlNodeReader $xaml)
            try {
                $Passphrases = [Windows.Markup.XamlReader]::Load( $reader )
            }
            catch {
                Write-Warning $_.Exception
                throw
            }

            $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                try {
                    Set-Variable -Name "Passphrases_$($_.Name)" -Value $Passphrases.FindName($_.Name) -ErrorAction Stop
                }
                catch {
                    throw
                }
            }
            _PassphrasesUIEvents
            #endregion
            Write-Output 'Loading Passphrases....'
            $null = $Passphrases.ShowDialog()
        }
        'WUG' {
            #region WUG
            $xamlFile = "$ModuleRoot\Assets\WhatsUpGold.xaml"

            #create window
            $inputXML = Get-Content $xamlFile -Raw
            $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
            [XML]$XAML = $inputXML
            $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

            #Read XAML
            $reader = (New-Object System.Xml.XmlNodeReader $xaml)
            try {
                $WhatsUpGold = [Windows.Markup.XamlReader]::Load( $reader )
            }
            catch {
                Write-Warning $_.Exception
                throw
            }

            $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                try {
                    Set-Variable -Name "WhatsUpGold_$($_.Name)" -Value $WhatsUpGold.FindName($_.Name) -ErrorAction Stop
                }
                catch {
                    throw
                }
            }
            _WhatsUpGoldUIEvents
            #endregion
            Write-Output 'Loading WUG Monitors....'
            $null = $WhatsUpGold.ShowDialog()
        }
        Default {
            #region RSOP
            if (!$RSOP) {
                $xamlFile = "$ModuleRoot\Assets\RSOP.xaml"

                #create window
                $inputXML = Get-Content $xamlFile -Raw
                $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
                [XML]$XAML = $inputXML
                $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

                #Read XAML
                $reader = (New-Object System.Xml.XmlNodeReader $xaml)
                try {
                    $RSOP = [Windows.Markup.XamlReader]::Load( $reader )
                }
                catch {
                    Write-Warning $_.Exception
                    throw
                }

                $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                    try {
                        Set-Variable -Name "RSOP_$($_.Name)" -Value $RSOP.FindName($_.Name) -ErrorAction Stop
                    }
                    catch {
                        throw
                    }
                }
                _RSOPWindowUIEvents
            }
            #endregion

            #region ADLookups
            if (!$ADLookups) {
                $xamlFile = "$ModuleRoot\Assets\ADLookups.xaml"

                #create window
                $inputXML = Get-Content $xamlFile -Raw
                $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
                [XML]$XAML = $inputXML
                $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

                #Read XAML
                $reader = (New-Object System.Xml.XmlNodeReader $xaml)
                try {
                    $ADLookups = [Windows.Markup.XamlReader]::Load( $reader )
                }
                catch {
                    Write-Warning $_.Exception
                    throw
                }

                $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                    try {
                        Set-Variable -Name "ADLookups_$($_.Name)" -Value $ADLookups.FindName($_.Name) -ErrorAction Stop
                    }
                    catch {
                        throw
                    }
                }
                _ADLookupsUIEvents
            }
            #endregion

            #region SCCMDeployments
            if (!$SCCMDeployments) {
                $xamlFile = "$ModuleRoot\Assets\SCCMDeployments.xaml"

                #create window
                $inputXML = Get-Content $xamlFile -Raw
                $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
                [XML]$XAML = $inputXML
                $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

                #Read XAML
                $reader = (New-Object System.Xml.XmlNodeReader $xaml)
                try {
                    $SCCMDeployments = [Windows.Markup.XamlReader]::Load( $reader )
                }
                catch {
                    Write-Warning $_.Exception
                    throw
                }

                $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                    try {
                        Set-Variable -Name "SCCMDeployments_$($_.Name)" -Value $SCCMDeployments.FindName($_.Name) -ErrorAction Stop
                    }
                    catch {
                        throw
                    }
                }
                _SCCMDeploymentsUIEvents
            }
            #endregion

            #region ComputerStats
            if (!$ComputerStats) {
                $xamlFile = "$ModuleRoot\Assets\ComputerStats.xaml"

                #create window
                $inputXML = Get-Content $xamlFile -Raw
                $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
                [XML]$XAML = $inputXML
                $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

                #Read XAML
                $reader = (New-Object System.Xml.XmlNodeReader $xaml)
                try {
                    $Global:ComputerStats = [Windows.Markup.XamlReader]::Load( $reader )
                }
                catch {
                    Write-Warning $_.Exception
                    throw
                }

                $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                    try {
                        Set-Variable -Name "ComputerStats_$($_.Name)" -Value $ComputerStats.FindName($_.Name) -Scope Global -ErrorAction Stop
                    }
                    catch {
                        throw
                    }
                }
                _ComputerStatsUIEvents
            }
            #endregion

            #region Passphrases
            if (!$Passphrases) {
                $xamlFile = "$ModuleRoot\Assets\Passphrases.xaml"

                #create window
                $inputXML = Get-Content $xamlFile -Raw
                $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
                [XML]$XAML = $inputXML
                $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

                #Read XAML
                $reader = (New-Object System.Xml.XmlNodeReader $xaml)
                try {
                    $Passphrases = [Windows.Markup.XamlReader]::Load( $reader )
                }
                catch {
                    Write-Warning $_.Exception
                    throw
                }

                $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                    try {
                        Set-Variable -Name "Passphrases_$($_.Name)" -Value $Passphrases.FindName($_.Name) -ErrorAction Stop
                    }
                    catch {
                        throw
                    }
                }
                _PassphrasesUIEvents
            }
            #endregion

            #region WUG
            if (!$WhatsUpGold) {
                $xamlFile = "$ModuleRoot\Assets\WhatsUpGold.xaml"

                #create window
                $inputXML = Get-Content $xamlFile -Raw
                $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
                [XML]$XAML = $inputXML
                $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

                #Read XAML
                $reader = (New-Object System.Xml.XmlNodeReader $xaml)
                try {
                    $WhatsUpGold = [Windows.Markup.XamlReader]::Load( $reader )
                }
                catch {
                    Write-Warning $_.Exception
                    throw
                }

                $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                    try {
                        Set-Variable -Name "WhatsUpGold_$($_.Name)" -Value $WhatsUpGold.FindName($_.Name) -ErrorAction Stop
                    }
                    catch {
                        throw
                    }
                }
                _WhatsUpGoldUIEvents
            }
            #endregion

            #region Options
            $xamlFile = "$ModuleRoot\Assets\Options.xaml"

            #create window
            $inputXML = Get-Content $xamlFile -Raw
            $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
            [XML]$XAML = $inputXML
            $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

            #Read XAML
            $reader = (New-Object System.Xml.XmlNodeReader $xaml)
            try {
                $Options = [Windows.Markup.XamlReader]::Load( $reader )
            }
            catch {
                Write-Warning $_.Exception
                throw
            }

            $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                try {
                    Set-Variable -Name "Options_$($_.Name)" -Value $Options.FindName($_.Name) -ErrorAction Stop
                }
                catch {
                    throw
                }
            }
            _OptionsUIEvents
            #endregion

            #region HomeWindow
            $xamlFile = "$ModuleRoot\Assets\HomeWindow.xaml"

            #create window
            $inputXML = Get-Content $xamlFile -Raw
            $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window'
            [XML]$XAML = $inputXML
            $xaml.window.'Window.Resources'.ResourceDictionary.'ResourceDictionary.MergedDictionaries'.ResourceDictionary.Source = $ThemeFile

            #Read XAML
            $reader = (New-Object System.Xml.XmlNodeReader $xaml)
            try {
                $HomeWindow = [Windows.Markup.XamlReader]::Load( $reader )
            }
            catch {
                Write-Warning $_.Exception
                throw
            }

            $xaml.SelectNodes('//*[@Name]') | ForEach-Object {
                try {
                    Set-Variable -Name "HomeWindow_$($_.Name)" -Value $HomeWindow.FindName($_.Name) -ErrorAction Stop
                }
                catch {
                    throw
                }
            }
            _HomeWindowUIEvents
            #endregion

            #region Update Check
            Write-Verbose 'Update Check'
            $UpdateCheck = _CheckForUpdate
            if ($UpdateCheck.Outdated) {
                $HomeWindow_UpdateStatusBarItem.Content = 'Update Found!'
                $MessageSplat = @{
                    MessageText  = "An update was found.`nCurrent version: $($UpdateCheck.Current)`nNew version: $($UpdateCheck.Newest)`nDo you want to update now?`n`nRelease Notes: $($UpdateCheck.ReleaseNotes)"
                    MessageIcon  = 'Asterisk'
                    ButtonType   = 'YesNo'
                    MessageTitle = 'Update Found'
                }
                $Answer = _ShowMessageBox @MessageSplat
                switch ($Answer) {
                    Yes {
                        Update-Module $ModuleName
                        Write-Host 'Module Updated. Open a new powershell session to load the new version.'
                        break
                    }
                    No {
                    }
                    Default {
                    }
                }
            }
            #endregion

            #region External Tools
            _ExternalTools
            #endregion

            Write-Output 'Loading ServerTeamTools....'
            $null = $HomeWindow.ShowDialog()
        }
    }
}