diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 460a085..dfe6fec 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -6,11 +6,11 @@ This Security Policy provides guidelines and procedures for maintaining the conf Due to the nature of the application being effectively just a PowerShell script, releases are only supported in active development. Should a security issue arise on your current version, patches only roll on newer updates, and there is no backwards support cycle currently in place. Users are expected to use the most recent version of the application for the best security and feature set. As of version 1.2.1, a built-in update path is now officially supported using the Windows Package Manager. -| Version | Supported | -| ------- | ------------------ | -| 1.0/1.1 | :stop_sign: - End of Support| -| 1.2.1 | :warning: - EOL with 1.3 rollout, autoupdate is supported | -| 1.3 | :white_check_mark: | +| Version | Supported | +| ----------------| ------------------ | +| 1.0, 1.1, 1.2 | :stop_sign: - End of Support| +| 1.3 | :warning: - EOL with 1.3.1 rollout, autoupdate is supported | +| 1.3.1 | :white_check_mark: | ## ETT-Admin Version Support diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..4cf998b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,107 @@ +## Enterprise Tech Tool — Copilot / AI Agent Guidance + +This repository is a single Windows PowerShell application with a small set of supporting modules and GUI helpers. The notes below capture the essential patterns, workflows, and examples an AI coding agent should know to be productive. + +### Big picture +- Primary entrypoint: `ETT.ps1` — loads `ETTConfig.json`, sets runtime flags, and dot-sources the helper scripts listed in `$Dependencies` (see the `$Dependencies` array in `ETT.ps1`). +- UI and features are defined by functions in `PSAssets/*.ps1` and `MiniClients/*.ps1` and then composed inside `ETT.ps1` (toolbox, tabs, and buttons). +- Packaging: authors use PS2EXE (see `Compiler/ps2exe.ps1`) to compile a portable EXE; runtime behavior differs when compiled vs running the raw PS1. + +### Developer / runtime workflows (explicit) +- Dev run (recommended for iterative edits): open PowerShell and dot-source `.\ETT.ps1` (or run it). Note: dot-sourcing in a compiled build behaves differently; many UI helpers and dot-sourced modules are intended for script execution. +- Build / compile: `Compiler/ps2exe.ps1` (project uses PS2EXE). Ensure PS2EXE is available in the environment. The README also documents winget packaging and the `EliWeitzman.ETT` package id. +- Auto-update and releases: `ETT.ps1` checks GitHub tags (API) for release tags; offline devices will skip update checks. + +### Project-specific conventions and patterns +- Custom functions intended for the GUI toolbox must be named with the `custom_` prefix to be auto-discovered (ETT loads functions and `Get-Command` filters for `custom_*`). +- Custom tools can also come from `ETTConfig.json` under `CustomFunctions`; each entry expects: `displayName`, `description`, `tab`, `requireAdmin`, and `codeBlock` (string containing the code to run). +- GUI construction uses a small set of composable helpers. Common helpers to reuse/patch: + - `Create-ETTButton` (ETT.ps1) — returns a WinForms Button wired to a ScriptBlock. + - `Create-ToolboxListItem` — returns PSCustomObject used in toolbox lists. + - `Create-ToolboxTabPage` — builds tabs and listboxes for toolbox items. + - `Create-GenericToolWindow` (PSAssets/GenericToolWindow.ps1) — standard pattern for AD/BitLocker windows. +- Admin-aware flow: many actions check `$adminmode` and either run logic inline or call `Start-Process -Verb RunAs` to elevate. Assume privileged actions must be guarded and tested on Windows with UAC prompts. + +### Integration points / external dependencies to be aware of +- RSAT / ActiveDirectory PowerShell module: many AD functions check `Get-Command -Name Get-ADComputer` and will disable GUI features if absent. Tests or CI must run on Windows with RSAT to exercise AD flows. +- Microsoft Graph (Get-MGContext / Connect-MgGraph) — used by Entra ID / BitLocker key retrieval in `MiniClients`. +- winget (Windows Package Manager) — used for app updates and referenced in README for install flow. +- Vendor CLIs (Dell/Lenovo command-line updaters) — code contains explicit checks for vendor-specific paths when invoking driver update logic. + +### Concrete editing examples (copy / paste friendly) +- Add a new toolbox action (place near other toolbox arrays in `ETT.ps1`): + + `# Example: add a quick diagnostic action` + `[void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "Quick Disk Health" -RequireAdmin $true -ScriptBlock { Start-Process powershell.exe -Verb RunAs -ArgumentList '-Command', 'chkdsk C:' }))` + +- Add a simple custom function (script scope) and let UI pick it up: + + `function custom_ShowHello { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('Hello from custom_ShowHello',0,'ETT',64) }` + +- Add a config-driven custom function to `ETTConfig.json` (example entry): + + `{ "displayName": "Show Random", "description": "Show random number", "tab":"Custom", "requireAdmin": false, "codeBlock": "$rand=(Get-Random -Minimum 1 -Maximum 100); $wshell=New-Object -ComObject Wscript.Shell; $wshell.Popup($rand,0,'Random',64)" } + +### Observed gotchas / edge cases (experimentally verified) +- Dot-sourcing vs compiled EXE: dot-sourcing helper scripts (`. $psFile`) works for development but compiled EXE builds will often hit the `catch` and skip dot-sourced loads — verify behavior after compilation. +- Platform: Windows-only. Tests or automation must run on Windows with PowerShell and required modules installed. +- Admin flows: UI shows a shield emoji for tools that require admin; ensure scripts that perform registry or BitLocker changes always verify `$adminmode`. +- Winget and GitHub API calls can fail on offline devices — code already catches and degrades, but changes to update logic should keep that in mind. + +### Files and locations you will reference most +- `ETT.ps1` — main app orchestration (load order, flags, `$Dependencies`). +- `ETTConfig.json` — runtime customization (brand color, AutoUpdateCheckerEnabled, CustomFunctions, Azure IDs). +- `PSAssets/ToolboxFunctions.ps1` — primary toolbox helper functions and many action implementations. +- `PSAssets/GenericToolWindow.ps1` — reusable GUI window builder (used by BitLocker and AD tools). +- `MiniClients/*.ps1` — small utilities (ADLookup, BitLocker, LAPS, etc.) used by toolbox tabs. +- `Compiler/ps2exe.ps1` — compile helper and intended packaging flow. + +### How to compile (PS2EXE) — quick recipe + +Summary: this project is typically distributed as a compiled EXE (PS2EXE). Development is easiest by dot-sourcing `ETT.ps1`. Use PS2EXE to build an EXE for portable or installer-based distribution. + +1) Install PS2EXE (optional if you already have `Compiler/ps2exe.ps1`): + +```powershell +# Install the community PS2EXE module (if needed) +Install-Module -Name ps2exe -Scope CurrentUser -Force +``` + +2) Basic compile (recommended starting command): + +```powershell +# From the repository root +# Uses the community ps2exe wrapper if installed; otherwise run the repo's Compiler/ps2exe.ps1 script similarly +Invoke-ps2exe -inputFile .\ETT.ps1 -outputFile .\dist\ETT.exe -iconFile .\ImageAssets\EnterpriseTechTool.ico -noConsole -x64 +``` + +If you prefer to call the included script directly (it may wrap options differently): + +```powershell +& '.\Compiler\ps2exe.ps1' -InputFile '.\ETT.ps1' -OutputFile '.\dist\ETT.exe' -x64 +``` + +Notes and recommended options +- Use `-x64` for 64-bit builds (recommended). The GUI and some MiniClients note "MUST COMPILE WITH x64". +- `-noConsole` removes the console window and produces a GUI-only EXE. +- Provide an `-iconFile` to brand the EXE; put an .ico in `ImageAssets/` and reference it. + +Dot-sourcing and embedding caveat +- `ETT.ps1` dot-sources `MiniClients/*.ps1` and `PSAssets/*.ps1` at runtime via `$Dependencies`. During compilation these dot-sources are wrapped in a try/catch (the code intentionally swallows errors for compiled mode). After compiling: + - Verify that the compiled EXE behaves as expected and that all UI modules are available. + - If a helper script is not embedding or running, either: embed its contents into `ETT.ps1` before compiling, or adjust the compile wrapper to include additional files (some ps2exe versions support an `-include` parameter). + +Quick verification checklist after building +- Run the compiled EXE on a Windows test machine. +- Confirm the app window appears and basic buttons (Clear Last Login, Get LAPS Password) open their windows. +- Test one admin and one non-admin flow (e.g., Start-WingetAppUpdates and Get-WindowsActivationKey) to confirm elevation behavior and UAC prompts. +- Check BitLocker and AD windows on a machine with RSAT / Microsoft Graph available to ensure those paths work. + +If anything fails, the two fastest remedies are: +- Re-run as a script (`.\ETT.ps1`) to get full error output (dot-sourcing provides easier debugging). +- Temporarily add verbose/logging output around the `$Dependencies` dot-source loop to confirm whether each helper file is loaded inside the EXE. + +If any of these sections are unclear or you'd like the file to be extended with examples for a specific task (e.g., add a new toolbox item, wire a new CustomFunction from JSON, or create a test harness), tell me which area to expand and I will iterate. + +--- +Please review these notes and tell me if you want additional examples (unit/test harness, or a short script to run local smoke-tests for common flows like: load UI, call a non-admin action, and call an admin action with elevation). diff --git a/ETT.ps1 b/ETT.ps1 index c750be2..ceddf1c 100644 --- a/ETT.ps1 +++ b/ETT.ps1 @@ -15,13 +15,13 @@ .AUTHOR Eli Weitzman .NOTES - Version: 1.3 + Version: 1.3.1 Creation Date: 12-26-22 .LICENSE BSD 3-Clause License - Copyright (c) 2024, Eli Weitzman + Copyright (c) 2026, Eli Weitzman Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -49,6 +49,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #> +# HELPER FUNCTIONS + # Create the Ternary Operator since PowerShell 5 doesn't have it set-alias ?: Invoke-Ternary -Option AllScope -Description "PSCX filter alias" filter Invoke-Ternary ([scriptblock]$decider, [scriptblock]$ifTrue, [scriptblock]$ifFalse) { @@ -60,6 +62,24 @@ filter Invoke-Ternary ([scriptblock]$decider, [scriptblock]$ifTrue, [scriptblock } } +function Create-ToastNotification{ + param ( + [System.Windows.Forms.ToolTipIcon]$Icon, + [string] $Title, + [string] $Message, + [int] $Duration + ) + #Create Toast Notification Stack + $ToastStack = New-Object System.Windows.Forms.NotifyIcon + $Path = 'C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe' + $ToastStack.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path) + $ToastStack.BalloonTipIcon = $Icon + $ToastStack.BalloonTipTitle = $Title + $ToastStack.BalloonTipText = $Message + $ToastStack.Visible = $true + $ToastStack.ShowBalloonTip($Duration) +} + #Load ETTConfig.json File $jsonConfigString = Get-Content -Path ".\ETTConfig.json" -ErrorAction SilentlyContinue $jsonConfig = $jsonConfigString | ConvertFrom-Json @@ -69,7 +89,7 @@ Add-Type -AssemblyName System.Windows.Forms [System.Windows.Forms.Application]::EnableVisualStyles() #Build Variables -$ETTVersion = "1.3" +$ETTVersion = "1.3.1" $AutoUpdateCheckerEnabled = (?: { $jsonConfig.AutoUpdateCheckerEnabled -ne $null -and $jsonConfig.AutoUpdateCheckerEnabled -ne "" } { $jsonConfig.AutoUpdateCheckerEnabled } { $true }) ## BEGIN INITIAL FLAGS - CHANGE THESE TO MATCH YOUR PREFERENCES @@ -130,10 +150,10 @@ Naming must follow this format in order to work: "custom_FUNCTIONNAME" #> #Custom Test Functions -function custom_ExampleFunction { +<#function custom_ExampleFunction { $wshell = New-Object -ComObject Wscript.Shell $wshell.Popup("This example function was triggered from a Custom Function list click.", 0, "Example Function", 0x1) -} +}#> <# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -208,15 +228,17 @@ if ($AutoUpdateCheckerEnabled -eq $true) { #Update Checker if ($applicationVersion -lt $githubVersion) { - $updatePrompt = [System.Windows.Forms.MessageBox]::Show("An update to ETT is available! Would you like to update now?", "Update Available", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Information) + $updatePrompt = [System.Windows.Forms.MessageBox]::Show("An update to ETT is available! Would you like to update now? This will close the current instance of ETT and may require an application restart.", "Update Available", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Information) if ($updatePrompt -eq "Yes") { #This is for if an application was installed with Winget, or with the self-extracting installer, and is a regular ETT variant if (($installType -eq "Installed")) { - winget.exe upgrade --id=EliWeitzman.ETT + Start-Process powershell.exe -ArgumentList "-command winget upgrade --id EliWeitzman.ETT" + exit } #If portable or PS1, refer that an update is available, and if yes, redirect to the repository to download the latest version elseif ($installType -eq "Portable") { Start-Process "https://github.com/eliweitzman/EnterpriseTechTool" + exit } } } @@ -326,11 +348,11 @@ $defenderEnrollmentStatus = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows $LoadingProgressBar.Value = 35 $LoadingLabel.Text = "Getting Manufacturer..." -$manufacturer = Get-WmiObject -Class Win32_ComputerSystemProduct | Select-Object -ExpandProperty Vendor +$manufacturer = Get-CimInstance -ClassName Win32_ComputerSystemProduct | Select-Object -ExpandProperty Vendor $LoadingProgressBar.Value = 40 $LoadingLabel.Text = "Getting Model..." -$model = Get-WmiObject -Class Win32_ComputerSystem -Property Model | Select-Object -ExpandProperty Model +$model = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -ExpandProperty Model $LoadingProgressBar.Value = 50 $LoadingLabel.Text = "Checking Hosts File..." @@ -348,8 +370,8 @@ $domain = (Get-CIMInstance -ClassName Win32_ComputerSystem).Domain $LoadingProgressBar.Value = 60 $LoadingLabel.Text = "Getting Drive Info..." -$drivespace = Get-WmiObject -ComputerName localhost -Class win32_logicaldisk | Where-Object caption -eq "C:" | foreach-object { Write-Output " $($_.caption) $('{0:N2}' -f ($_.Size/1gb)) GB total, $('{0:N2}' -f ($_.FreeSpace/1gb)) GB free " } -$freedrivespace = Get-WmiObject -ComputerName localhost -Class win32_logicaldisk | Where-Object caption -eq "C:" | foreach-object { Write-Output $('{0:N2}' -f ($_.FreeSpace / 1gb)) } +$drivespace = Get-CimInstance -ClassName win32_logicaldisk | Where-Object caption -eq "C:" | foreach-object { Write-Output " $($_.caption) $('{0:N2}' -f ($_.Size/1gb)) GB total, $('{0:N2}' -f ($_.FreeSpace/1gb)) GB free " } +$freedrivespace = Get-CimInstance -ClassName win32_logicaldisk | Where-Object caption -eq "C:" | foreach-object { Write-Output $('{0:N2}' -f ($_.FreeSpace / 1gb)) } $LoadingProgressBar.Value = 70 $LoadingLabel.Text = "Getting RAM Info..." @@ -377,11 +399,11 @@ else { $LoadingProgressBar.Value = 85 $LoadingLabel.Text = "Getting CPU Info..." -$cpuCheck = Get-WmiObject -Class Win32_Processor | Select-Object -ExpandProperty Name +$cpuCheck = Get-CimInstance -ClassName Win32_Processor | Select-Object -ExpandProperty Name $LoadingProgressBar.Value = 90 $LoadingLabel.Text = "Getting Device Type..." -$devicetype = (Get-WmiObject -Class Win32_ComputerSystem -Property PCSystemType).PCSystemType +$devicetype = (Get-CimInstance -ClassName Win32_ComputerSystem -Property PCSystemType).PCSystemType $LoadingProgressBar.Value = 95 $LoadingLabel.Text = "Getting Drive Type..." @@ -703,8 +725,9 @@ function Create-ToolboxTabPage { $tmpTab.Controls.Add($tmpList) $tmpList.DataSource = $ToolboxItemsArray $tmpList.DisplayMember = "displayName" - $tmpList.ValueMember = "codeBlock" - + if ($ToolboxItemsArray -and $ToolboxItemsArray[0].PSObject.Properties.Match("codeBlock")) { + $tmpList.ValueMember = "codeBlock" + } return $tmpList } @@ -761,27 +784,16 @@ if ($null -ne $ETT.BackgroundImage) { $Heading.ForeColor = $ettHeaderTextColor } -#Create Toast Notification Stack -$ToastStack = New-Object System.Windows.Forms.NotifyIcon -$Path = 'C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe' -$ToastStack.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path) -$ToastStack.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info -$ToastStack.BalloonTipTitle = $ettApplicationTitle -$ToastStack.BalloonTipText = "Welcome to $ettApplicationTitle!" -$ToastStack.Visible = $true -$ToastStack.ShowBalloonTip(5000) +#Display Welcome Toast +Create-ToastNotification -Icon "Info" -Title $ettApplicationTitle -Message "Welcome to $ettApplicationTitle!" -Duration 5000 #IF Compliance Flag is true, add a flyout notification if ($complianceFlag -eq $true) { - $ToastStack.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Error - $ToastStack.BalloonTipTitle = $ettApplicationTitle - $ToastStack.BalloonTipText = "This device is non-compliant!" - $ToastStack.Visible = $true - $ToastStack.ShowBalloonTip(5000) + Create-ToastNotification -Icon [System.Windows.Forms.ToolTipIcon]::Error -Title $ettApplicationTitle -Message "This device is non-compliant!" -Duration 5000 } #Create App Buttons -$ClearLastLogin = Create-ETTButton -ButtonText "Clear Last Login" -ButtonWidth 237 -ButtonHeight 89 -ButtonXPosition 13 -ButtonYPosition 117 -ScriptBlock { ClearLastLogin -adminmode $adminmode -ToastStack $ToastStack } +$ClearLastLogin = Create-ETTButton -ButtonText "Clear Last Login" -ButtonWidth 237 -ButtonHeight 89 -ButtonXPosition 13 -ButtonYPosition 117 -ScriptBlock { ClearLastLogin -adminmode $adminmode} $Lapspw = Create-ETTButton -ButtonText "Get LAPS Password" -ButtonWidth 237 -ButtonHeight 89 -ButtonXPosition 267 -ButtonYPosition 117 -ScriptBlock { Open-LAPSToolWindow } $appUpdate = Create-ETTButton -ButtonText "Update Apps (Winget)" -ButtonWidth 237 -ButtonHeight 89 -ButtonXPosition 13 -ButtonYPosition 219 -ScriptBlock { Start-WingetAppUpdates } $PolicyPatch = Create-ETTButton -ButtonText "Windows Policy Update" -ButtonWidth 237 -ButtonHeight 89 -ButtonXPosition 266 -ButtonYPosition 219 -ScriptBlock { Start-PolicyPatch } @@ -814,12 +826,14 @@ $ETT.Controls.Add($ToolboxMenu) | Out-Null $ActionsTabArray = New-Object System.Collections.ArrayList [void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "Driver Updater (GUI)" -ScriptBlock { Start-DriverUpdateGUI -manufacturer $manufacturer })) [void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "Driver Updater (CLI)" -ScriptBlock { Start-DriverUpdateCLI -manufacturer $manufacturer })) -[void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "SFC Scan" -RequireAdmin $true -ScriptBlock { Start-SFCScan })) [void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "Suspend Bitlocker" -RequireAdmin $true -ScriptBlock { Start-SuspendBitlockerAction -adminmode $adminmode })) [void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "Test Network" -ScriptBlock { Start-NetworkTest })) [void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "WiFi Diagnostics" -RequireAdmin $true -ScriptBlock { Start-WiFiDiagnostics -adminmode $adminmode })) [void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "Battery Diagnostics" -RequireAdmin $true -ScriptBlock { Start-BatteryDiagnostics -adminmode $adminmode })) [void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "Quick Reboot" -ScriptBlock { QuickReboot })) +[void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "Repair Outlook PST File" -RequireAdmin $true -ScriptBlock { Repair-OutlookPST -adminmode $adminmode })) +[void]$ActionsTabArray.Add((Create-ToolboxListItem -Displayname "Restore to Outlook (classic)" -RequireAdmin $true -ScriptBlock {Restore-OldOutlook -adminmode $adminmode })) +[void]$ActionsTabArray.Add((Create-ToolboxListItem -DisplayName "Block Automatic New Outlook Migration" -RequireAdmin $true -ScriptBlock { Disable-AutomaticNewOutlookMigration -adminmode $adminmode })) $ActionsTab = Create-ToolboxTabPage -PageName "Actions" -ToolboxItemsArray $ActionsTabArray $ActionsTab.Add_Click({ $runThis = [ScriptBlock]::Create($ActionsTab.SelectedValue) @@ -830,27 +844,32 @@ $ActionsTab.Add_Click({ $WindowsTabArray = New-Object System.Collections.ArrayList [void]$WindowsTabArray.Add((Create-ToolboxListItem -DisplayName "Windows Update - Full Sweep" -ScriptBlock { CheckForWindowsUpdates -windowTitle "All Windows Updates" -noUpdatesMessage "No updates available." -updateSearchQuery "IsHidden=0 and IsInstalled=0" })) [void]$WindowsTabArray.Add((Create-ToolboxListItem -DisplayName "Windows Update - Defender Only" -ScriptBlock { CheckForWindowsUpdates -windowTitle "Windows Defender Definition Updates" -noUpdatesMessage "No Windows Defender Definition updates found." -updateSearchQuery "IsInstalled=0 and Type='Software' and IsHidden=0 and BrowseOnly=0 and AutoSelectOnWebSites=1 and CategoryIDs contains '8c3fcc84-7410-4a95-8b89-a166a0190486'" })) -[void]$WindowsTabArray.Add((Create-ToolboxListItem -DisplayName "Get Windows Activation" -ScriptBlock { Get-WindowsActivationKey })) -[void]$WindowsTabArray.Add((Create-ToolboxListItem -DisplayName "Get Windows Activation Type" -ScriptBlock { Get-WindowsActivationType })) +[void]$WindowsTabArray.Add((Create-ToolboxListItem -DisplayName "Windows Activation - Get Activation Key" -ScriptBlock { Get-WindowsActivationKey })) +[void]$WindowsTabArray.Add((Create-ToolboxListItem -DisplayName "Windows Activation - Get Activation Type" -ScriptBlock { Get-WindowsActivationType })) +[void]$WindowsTabArray.Add((Create-ToolboxListItem -DisplayName "Windows Repair - SFC Scan" -RequireAdmin $true -ScriptBlock { Start-SFCScan })) +[void]$WindowsTabArray.Add((Create-ToolboxListItem -DisplayName "Windows Repair - DISM Online Repair" -RequireAdmin $true -ScriptBlock { Start-DISMScan })) +[void]$WindowsTabArray.Add((Create-ToolboxListItem -DisplayName "Windows Group Policy - Delete GPO Cache" -RequireAdmin $true -ScriptBlock { Clear-GroupPolicyCache})) $WindowsTab = Create-ToolboxTabPage -PageName "Windows" -ToolboxItemsArray $WindowsTabArray $WindowsTab.Add_Click({ $runThis = [ScriptBlock]::Create($WindowsTab.SelectedValue) &$runThis }) - + #Tab 3 - Security Tab Creation $SecurityTabArray = New-Object System.Collections.ArrayList -[void]$SecurityTabArray.Add((Create-ToolboxListItem -DisplayName "$(Get-HostsFileIntegrity)" -ScriptBlock {})) +#[void]$SecurityTabArray.Add((Create-ToolboxListItem -DisplayName "$(Get-HostsFileIntegrity)" -ScriptBlock {Show-HostsFileIntegrityPopup})) +[void]$SecurityTabArray.Add((Create-ToolboxListItem -DisplayName "Windows Defender - Launch Full Scan" -ScriptBlock {Start-DefenderFullScan})) +[void]$SecurityTabArray.Add((Create-ToolboxListItem -DisplayName "Windows Defender - Launch Quick Scan" -ScriptBlock {Start-DefenderQuickScan})) $SecurityTab = Create-ToolboxTabPage -PageName "Security" -ToolboxItemsArray $SecurityTabArray $SecurityTab.Add_Click({ $runThis = [ScriptBlock]::Create($SecurityTab.SelectedValue) &$runThis }) - + #Tab 4 - SCCM (if enabled) Tab Creation #Check to see if the SCCM client is installed and we have the required WMI class -$sccmClass = Get-WmiObject -Class "SMS_Client" -List -Namespace "root\CCM" -ErrorAction SilentlyContinue +$sccmClass = Get-CimInstance -ClassName "SMS_Client" -Namespace "root\CCM" -ErrorAction SilentlyContinue $sccmClassExists = $sccmClass -ne $null if ($sccmClassExists) { @@ -1314,6 +1333,7 @@ $menuFunctions.DropDownItems.Add($menuRenameComputer) $menuSettings.Text = "Settings" $menuSettings.Add_Click({ #First, check to see if running in admin mode + if (($adminmode -eq $true) -or ($installType -eq "Portable")) { #Next, check to see if ETTConfig.json exists in the same directory as the script $configtest = Test-Path ".\ETTConfig.json" -ErrorAction SilentlyContinue @@ -1346,7 +1366,7 @@ $menuSettings.Add_Click({ "DriveSpaceCheckActive" : false, "DriveSpaceCheckMinimum" : 20, "WinVersionCheckActive" : false, - "WinVersionTarget": "24H2", + "WinVersionTarget": "25H2", "AzureADTenantId" : "", "LAPSAppClientId" : "", "BitLockerAppClientId" : "", @@ -1389,7 +1409,7 @@ $menuSettings.Add_Click({ $wshell = New-Object -ComObject Wscript.Shell $wshell.Popup("You must be in Admin Mode to access settings.", 0, "Settings Error", 48) } - }) +}) [void]$menu.Items.Add($menuSettings) #Exit Button @@ -1397,6 +1417,14 @@ $menuExit.Text = "Exit" $menuExit.Add_Click({ $ETT.Close() }) [void]$menu.Items.Add($menuExit) +#Adding a keyboard shortcut to the Exit button - escape key +$ETT.KeyPreview = $true +$ETT.Add_KeyDown({ + if ($_.KeyCode -eq "Escape") { + $ETT.Close() + } + }) + #Add all buttons and functions to the GUI menu $ETT.controls.AddRange(@($Logo, $Heading, $ClearLastLogin, $Lapspw, $appUpdate, $PolicyPatch, $menu)) diff --git a/ETTConfig.json b/ETTConfig.json index 3688326..0c02eb9 100644 --- a/ETTConfig.json +++ b/ETTConfig.json @@ -16,7 +16,7 @@ "DriveSpaceCheckActive" : false, "DriveSpaceCheckMinimum" : 20, "WinVersionCheckActive" : false, - "WinVersionTarget": "24H2", + "WinVersionTarget": "25H2", "DefenderEnrollCheckActive" : false, "AzureADTenantId" : "", "LAPSAppClientId" : "", diff --git a/LICENSE b/LICENSE index b7fd964..3a6e51c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2024, Eli Weitzman +Copyright (c) 2026, Eli Weitzman Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/MiniClients/SettingsMenu.ps1 b/MiniClients/SettingsMenu.ps1 index c61e1e1..62d7350 100644 --- a/MiniClients/SettingsMenu.ps1 +++ b/MiniClients/SettingsMenu.ps1 @@ -691,10 +691,16 @@ function Open-SettingsMenu { #Show the form $settingsForm.ShowDialog() } - else { + elseif ($SettingsMenuEnabled -eq $false) { #ELSE - show popup that this has been by configuration file. [System.Windows.Forms.MessageBox]::Show("Settings have been disabled by config.", "Settings Disabled", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information) + }else{ + #ELSE - show popup that settings menu is not referenced in config, and recommend updating the config file with the latest from the ETT repository. + [System.Windows.Forms.MessageBox]::Show("Settings menu is not referenced in config. Please update the configuration file with the latest from the ETT repository.", "Settings Not Referenced", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) + $openRepo = [System.Windows.Forms.MessageBox]::Show("Would you like to open the ETT GitHub repository to download the latest configuration file?", "Open Repository", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question) + if ($openRepo -eq [System.Windows.Forms.DialogResult]::Yes) { + #Open the ETT GitHub repository + Start-Process "https://github.com/eliweitzman/EnterpriseTechTool/blob/main/ETTConfig.json" + } } - - } \ No newline at end of file diff --git a/PSAssets/ToolboxFunctions.ps1 b/PSAssets/ToolboxFunctions.ps1 index b61dba2..6a13702 100644 --- a/PSAssets/ToolboxFunctions.ps1 +++ b/PSAssets/ToolboxFunctions.ps1 @@ -83,35 +83,37 @@ function CheckForWindowsUpdates { function ClearLastLogin { param( [Parameter(Position = 0, mandatory = $true)] - $adminmode, - [Parameter(Position = 1, mandatory = $true)] - $ToastStack + $adminmode ) - #Check if admin mode is enabled. Depending on the result, run the appropriate command - if ($adminmode -eq $true) { - #With admin mode enabled, run the commands without UAC - New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI' -Name LastLoggedOnSAMUser -Value "" -Force - New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI' -Name LastLoggedOnUser -Value "" -Force - New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI' -Name LastLoggedOnUserSID -Value "" -Force - } - elseif ($adminmode -eq $false) { - #Without admin mode enabled, run the commands with UAC, in a sub-process shell - Start-Process powershell.exe -Verb runAs -ArgumentList '-Command', 'New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'' -Name LastLoggedOnSAMUser -Value "" -Force; New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'' -Name LastLoggedOnUser -Value "" -Force; New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'' -Name LastLoggedOnUserSID -Value "" -Force' -Wait - } - - #Display a notification that the last login has been cleared - $ToastStack.BalloonTipText = "Last Login Cleared!" - $ToastStack.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info - $ToastStack.BalloonTipTitle = "Login Status" - $ToastStack.ShowBalloonTip(5000) - $ToastStack.Visible = $true + try{ + #Check if admin mode is enabled. Depending on the result, run the appropriate command + if ($adminmode -eq $true) { + #With admin mode enabled, run the commands without UAC + New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI' -Name LastLoggedOnSAMUser -Value "" -Force + New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI' -Name LastLoggedOnUser -Value "" -Force + New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI' -Name LastLoggedOnUserSID -Value "" -Force + } + elseif ($adminmode -eq $false) { + #Without admin mode enabled, run the commands with UAC, in a sub-process shell + Start-Process powershell.exe -Verb runAs -ArgumentList '-Command', 'New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'' -Name LastLoggedOnSAMUser -Value "" -Force; New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'' -Name LastLoggedOnUser -Value "" -Force; New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'' -Name LastLoggedOnUserSID -Value "" -Force' -Wait + } + #Display a notification that the last login has been cleared + Create-ToastNotification -Icon Info -Title "Login Status" -Message "Last Login Cleared!" -Duration 5000 + } + catch + { + #Display a notification that the last login has not been cleared + Create-ToastNotification -Icon Error -Title "Login Status" -Message "Last Login didn't clear due to an error. Please make sure that you are allowing the function to run under administrative context and try again." -Duration 5000 + } + + } function Get-WindowsActivationKey { - $HardwareKey = (Get-WmiObject -query 'select * from SoftwareLicensingService' | Select-Object OA3xOriginalProductKey).OA3xOriginalProductKey + $HardwareKey = (Get-CimInstance -Query 'select * from SoftwareLicensingService' | Select-Object OA3xOriginalProductKey).OA3xOriginalProductKey #Verify that the key is not null - if ($HardwareKey -eq $null -or $HardwareKey -eq "") { + if ($null -eq $HardwareKey -or $HardwareKey -eq "") { $wshell = New-Object -ComObject Wscript.Shell $wshell.Popup("No Windows Activation Key found in WMI." + "`n`nThis could be the result of running in a VM, or not stored in BIOS", 0, "Windows Activation", 64) } @@ -122,6 +124,7 @@ function Get-WindowsActivationKey { $wshell.Popup("Windows Activation Key: " + $HardwareKey + "`n`nKey Copied to Clipboard.", 0, "Windows Activation Key", 64) } } +<# function Get-HostsFileIntegrity { $hostsHash = (Get-FileHash "C:\Windows\System32\Drivers\etc\hosts").Hash $hostsCompliant = $true @@ -133,6 +136,13 @@ function Get-HostsFileIntegrity { } } +function Show-HostsFileIntegrityPopup { + $hostsText = Get-HostsFileIntegrity + $wshell = New-Object -ComObject Wscript.Shell + $wshell.Popup($hostsText, 0, "Hosts File Integrity", 64) +} + +#> function Get-WindowsActivationType { slmgr.vbs /dli } @@ -203,18 +213,13 @@ function Start-DriverUpdateGUI { } } -function Start-SFCScan { - #SFC Scan - Start-Process powershell.exe -ArgumentList "-command sfc /scannow" -PassThru -Verb RunAs -} - function Start-SuspendBitlockerAction { param( [Parameter(Position = 0, mandatory = $true)] $adminmode ) #Check if adminmode is enabled - if ($adminmode -eq "True") { + if ($adminmode -eq $true) { #Check if BitLocker is enabled if ((Get-BitLockerVolume -MountPoint C:).ProtectionStatus -eq "On") { #Suspend BitLocker @@ -246,7 +251,7 @@ function Start-WiFiDiagnostics { $adminmode ) #Test Wi-Fi - if ($adminmode -eq "True") { + if ($adminmode -eq $true) { Start-Process cmd.exe -ArgumentList "/K netsh wlan show wlanreport" -PassThru -Wait Start-Process "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" -ArgumentList "C:\ProgramData\Microsoft\Windows\WlanReport\wlan-report-latest.html" -WindowStyle maximized } @@ -270,7 +275,7 @@ function Start-BatteryDiagnostics { #Test Battery, first check if device is a laptop if ($systemType -eq "Mobile" -or $systemType -eq "Appliance PC" -or $systemType -eq "Slate") { #Device is a laptop, now check if adminmode is enabled - if ($adminmode -eq "True") { + if ($adminmode -eq $true) { #Open a save dialog to save the battery report $saveFileDialog = New-Object System.Windows.Forms.SaveFileDialog $saveFileDialog.Filter = "HTML files (*.html)|*.html" @@ -324,6 +329,46 @@ function Start-SCCMClientFunction { $wshell.Popup("SCCM Client Task $TriggerScheduleName Triggered. The selected task will run and might take several minutes to finish.", 0, "SCCM Client Task", 64) } +function Start-SFCScan { + if ($adminmode -eq $true) { + Start-Process powershell.exe -ArgumentList "-command sfc /scannow" -PassThru -Wait + } + else { + Start-Process powershell.exe -ArgumentList "-command sfc /scannow" -PassThru -Verb RunAs -Wait + } +} + +function Start-DISMScan { + if ($adminmode -eq $true) { + Start-Process powershell.exe -ArgumentList "-command DISM.exe /Online /Cleanup-image /Restorehealth" -PassThru -Wait + } + else { + Start-Process powershell.exe -ArgumentList "-command DISM.exe /Online /Cleanup-image /Restorehealth" -PassThru -Verb RunAs -Wait + } +} + +function Start-DefenderFullScan { + if ($wshell.Popup("Are you sure you want to run a Defender Full Scan?", 0, "Defender Full Scan", 4 + 32) -eq 6) { + try { + Start-MpScan -ScanType FullScan -ErrorAction Stop + } + catch [Microsoft.Management.Infrastructure.CimException] { + $wshell.Popup($_.Exception.Message, 0, "Defender Full Scan", 0 + 16) + } + } +} + +function Start-DefenderQuickScan { + if ($wshell.Popup("Are you sure you want to run a Defender Quick Scan?", 0, "Defender Quick Scan", 4 + 32) -eq 6) { + try { + Start-MpScan -ScanType QuickScan -ErrorAction Stop + } + catch [Microsoft.Management.Infrastructure.CimException] { + $wshell.Popup($_.Exception.Message, 0, "Defender Quick Scan", 0 + 16) + } + } +} + function QuickReboot { #First, confirm reboot $wshell = New-Object -ComObject Wscript.Shell @@ -331,4 +376,89 @@ function QuickReboot { #Reboot Start-Process shutdown -argumentlist "-r -t 0" -PassThru } +} + +function Clear-GroupPolicyCache { + $wshell = New-Object -ComObject Wscript.Shell + if ($adminmode -eq $true) { + if ($wshell.Popup("Are you sure you want to clear the Group Policy Cache?", 0, "Group Policy Cache", 4 + 32) -eq 6) { + try{ + # Stop the Group Policy Client service + Stop-Service -Name gpsvc -Force -ErrorAction Stop + + # Delete the Group Policy cache folders + $gpCacheFolders = @( + "C:\Windows\System32\GroupPolicy", + "C:\Windows\System32\GroupPolicyUsers" + ) + + foreach ($folder in $gpCacheFolders) { + if (Test-Path -Path $folder) { + Remove-Item -Path $folder -Recurse -Force + } + } + + # Start the Group Policy Client service + Start-Service -Name gpsvc -ErrorAction Stop + $wshell.Popup("Group Policy Cache has been deleted.", 0, "Group Policy Cache", 0 + 64) + } + catch { + $wshell.Popup("There was an error deleting the GPO Cache. Please make sure this machine is joined to a domain and try again.", 0, "Group Policy Cache", 0 + 16) + } + } + } + else { + $wshell.Popup("Please run the Delete Group Policy Cache Function as an administrator by restarting ETT in Admin Mode!", 0, "Group Policy Cache", 0 + 16) + } +} + +function Repair-OutlookPST { + $wshell = New-Object -ComObject Wscript.Shell + if ($adminmode -eq $true) { + if ($wshell.Popup("Are you sure you want to repair the Outlook PST file?", 0, "Outlook PST Repair", 4 + 32) -eq 6) { + try { + Start-Process -FilePath "C:\Program Files\Microsoft Office\root\Office16\SCANPST.EXE" -Verb RunAs + } + catch { + $wshell.Popup("There was an error repairing the Outlook PST file. Please make sure Outlook is closed and try again.", 0, "Outlook PST Repair", 0 + 16) + } + } + } + else { + #Pass admin request to user + Start-Process -FilePath "C:\Program Files\Microsoft Office\root\Office16\SCANPST.EXE" -Verb RunAs -PassThru + } +} + +function Restore-OldOutlook { + #Using registry key to rollback Outlook + $wshell = New-Object -ComObject Wscript.Shell + if ($adminmode -eq $true) { + try { + New-ItemProperty -Path 'HKCU:\Software\Microsoft\Office\16.0\Outlook\Preferences' -Name UseNewOutlook -Value "0" -Force + $wshell.Popup("Outlook has been reverted back to Outlook (classic).", 0, "Rollback Outlook", 0 + 64) + } + catch { + Create-ToastNotification -Icon Error -Title "Outlook Rollback" -Message "There was an error rolling back Outlook. Please make sure legacy Outlook is installed and try again." -Duration 5000 + } + } + else { + #Show a toast notification to run in admin mode + Create-ToastNotification -Icon Error -Title "Administrator Required" -Message "Please run the Restore Old Outlook function as an administrator by restarting ETT in Admin Mode!" -Duration 5000 + } +} + +function Disable-AutomaticNewOutlookMigration { + $wshell = New-Object -ComObject Wscript.Shell + if ($adminmode -eq $true) { + try { + New-ItemProperty -Path 'HKCU:\Software\Policies\Microsoft\office\16.0\outlook\preferences' -Name NewOutlookMigrationUserSetting -Value "0" -Force + $wshell.Popup("Automatic Outlook Migration has been disabled.", 0, "Outlook Migration", 0 + 64) + } + catch { + Create-ToastNotification -Icon Error -Title "Issue with Outlook Rollback Blocker" -Message "There was an error modifying the registry keys. Please make sure legacy Outlook is installed and try again." -Duration 5000 + } + } else { + Create-ToastNotification -Icon Error -Title "Administrator Required" -Message "Please run the Disable Automatic New Outlook Migration function as an administrator by restarting ETT in Admin Mode!" -Duration 5000 + } } \ No newline at end of file