MaxKK

PABS 1.31

Apr 19th, 2026 (edited)
194
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PowerShell 13.26 KB | Software | 0 0
  1. # ==============================================================================
  2. # PABS: Powerplan And Brightness Selector (v1.31)
  3. # A lightweight utility to automate Windows Power Plans and Brightness levels.
  4. # written by MaxK https://www.reddit.com/user/Glad-Journalist-4807/
  5. # on ThinkPad X390/T14s gen 1 hybrid
  6. # Please keep the credits intact :)
  7. #
  8. #   Changes in v1.31:
  9. # 1. Added verification when loading power plans from config file in case some of them were deleted
  10. # 2. Added a bit more logging
  11. # 3. Replaced Start-Process "powercfg.exe" with direct call - it's faster.
  12. #   Changes in v1.3:
  13. # 1. Added "PABS_Config.txt" – automated generation and parsing of configuration file.
  14. # 2. Dynamic Power Plan Discovery – automatically finds all system GUIDs (including hidden ones).
  15. # 3. Critical Performance Optimization
  16. #   – refactored process scanning using .NET and SessionID filtering.
  17. #   - CPU usage reduction – achieved 3x lower CPU usage by eliminating WMI overhead and exceptions.
  18. # 4. High-resolution stopwatch for internal performance monitoring and Measure-Command for critical path monitoring (when $logCriticalPath=1)
  19. # 5. UI Optimization – tray icon and status updates are now event-driven (only on actual change).
  20. # 6. Added logging to file (when $logToFile=1)
  21. #   Changes in v1.2:
  22. # 1. PS 7 compatibility corrections: replaced ContextMenu with ContextMenuStrip, MenuItems -> Items and MenuItem ->ToolStripMenuItem.
  23. # ==============================================================================
  24.  
  25. # CONFIGURATION
  26.  
  27. $PABSversion = "1.31"
  28. $ConfigFile = Join-Path $PSScriptRoot "PABS_Config.txt"
  29. $logCriticalPath = 0
  30. $logToFile = 0
  31. $logFile = Join-Path $PSScriptRoot "PABS_Log.txt"
  32.  
  33.  
  34.  
  35. function Write-Log ($msg, $color = "White") {
  36.    
  37.     $timestamp = Get-Date -Format "HH:mm:ss:fff"
  38.     $logLine = "[$timestamp] $msg"    
  39.     Write-Host $logLine -ForegroundColor $color
  40.     if ($logToFile -eq 1) {
  41.        
  42.          try {
  43.               $logLine | Out-File $logFile -Append -Encoding utf8
  44.          } catch {
  45.    
  46.          }
  47.     }
  48. }
  49.  
  50.  
  51. function Initialize-PABSConfig {
  52.     if (Test-Path $ConfigFile) { return }
  53.  
  54.     Write-Log "First run: Generating PABS_Config.txt..." -ForegroundColor Yellow
  55.     $output = @(
  56.         "# PABS CONFIGURATION FILE",
  57.         "# =======================",
  58.         "# 1. PowerPlan Definition: PowerPlan=Name,GUID",
  59.         "# 2. Default Plans: DefaultAC=Name | DefaultBat=Name",
  60.         "# 3. Watchlist Rules: Mode | Path | PlanName",
  61.         ""
  62.     )
  63.    
  64.    
  65.     $sysPlans = powercfg /l | Where-Object { $_ -match "GUID:" }
  66.     $foundPlans = @()
  67.     foreach ($line in $sysPlans) {
  68.         if ($line -match "GUID:\s+([\w-]+)\s+\((.+)\)") {
  69.             $guid = $matches[1]; $name = $matches[2].Replace("*", "").Trim()
  70.             $output += "PowerPlan=$name,$guid"
  71.             $foundPlans += $name
  72.         }
  73.     }
  74.  
  75.     $output += @(
  76.         "",
  77.         "# DEFAULTS",
  78.         "DefaultAC=Balanced",
  79.         "DefaultBat=Power Saver",
  80.         "",
  81.         "# WATCHLIST RULES (First match wins)",
  82.         "# Format: AC/Bat | Path | PlanName",
  83.         "AC  | C:\Games\* | Ultimate Performance",
  84.         "AC  | C:\Program Files\Mozilla Firefox\* | High Performance",
  85.         "Bat | C:\Games\* | Balanced",
  86.         "Bat | C:\Program Files\* | Power Saver"
  87.     )
  88.    
  89.     $output | Out-File $ConfigFile -Encoding utf8
  90. }
  91.  
  92.  
  93. $PowerPlans = @{}
  94. $WatchListAC = @()
  95. $WatchListBattery = @()
  96. $DefaultPPAC = "Balanced"
  97. $DefaultPPBattery = "Power Saver"
  98.  
  99. Initialize-PABSConfig
  100.  
  101. # get config
  102. if (Test-Path $ConfigFile) {
  103.     Get-Content $ConfigFile | ForEach-Object {
  104.         $line = $_.Trim()
  105.         if ($line -match "^PowerPlan=(.+),(.+)") {
  106.             $PowerPlans[$matches[1].Trim()] = $matches[2].Trim()
  107.         }
  108.         elseif ($line -match "^DefaultAC=(.+)") {
  109.             $DefaultPPAC = $matches[1].Trim()
  110.         }
  111.         elseif ($line -match "^DefaultBat=(.+)") {
  112.             $DefaultPPBattery = $matches[1].Trim()
  113.         }
  114.         elseif ($line -match "^(AC|Bat)\s*\|\s*(.+)\s*\|\s*(.+)") {
  115.             $mode = $matches[1].Trim()
  116.             $rule = @{ Path = $matches[2].Trim(); Plan = $matches[3].Trim() }
  117.             if ($mode -eq "AC") { $WatchListAC += $rule } else { $WatchListBattery += $rule }
  118.         }
  119.     }
  120. }
  121.  
  122. # verify plans
  123. $Aliases = @($PowerPlans.Keys)
  124. foreach ($alias in $Aliases) {
  125.     $guid = $PowerPlans[$alias]
  126.    
  127.     $check = powercfg /l | Select-String $guid
  128.    
  129.     if (-not $check) {
  130.         Write-Log "WARNING: Power Plan '$alias' ($guid) not found! Removing from list." "Red"
  131.         $PowerPlans.Remove($alias)
  132.     }
  133. }
  134.  
  135. $Global:PABS_Timer = [System.Diagnostics.Stopwatch]::StartNew()
  136.  
  137. # When set to 1, the current brightness level is preserved and set after switching plans
  138. $AutoInheritBrightness = 1
  139.  
  140. # Interval for checking running processes and switching plans (in seconds)
  141. $ProcessCheckInterval = 5
  142.  
  143. # Interval for polling keyboard state (Ctrl+Shift+B) in milliseconds
  144. $KeyCheckInterval = 80
  145.  
  146. # Standard Windows GUID for the Video/Display subgroup
  147. $DisplaySubGroup = "7516b95f-f776-4464-8c53-06167f40cc99"
  148.  
  149. # end of CONFIGURATION
  150.  
  151. #WIN32 API&UTILS
  152.  
  153. $WinFuncs = @"
  154. [DllImport("user32.dll")] public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
  155. [DllImport("user32.dll")] public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
  156. [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
  157. [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
  158. [DllImport("user32.dll")] public static extern short GetAsyncKeyState(int vKey);
  159. [DllImport("powrprof.dll")] public static extern uint PowerGetActiveScheme(IntPtr UserRootPowerKey, out IntPtr ActivePolicyGuiPtr);
  160. "@
  161. $Win32 = Add-Type -MemberDefinition $WinFuncs -Name "Win32Utils" -Namespace Win32 -PassThru
  162.  
  163. # get console window
  164. $ConsolePtr = (Get-Process -Id $PID).MainWindowHandle
  165.  
  166. # disable the "X" (Close) button to prevent accidental exit
  167. function Disable-CloseButton {
  168.     $hMenu = [Win32.Win32Utils]::GetSystemMenu($ConsolePtr, $false)
  169.     [void][Win32.Win32Utils]::EnableMenuItem($hMenu, 0xF060, 0x00000001)
  170. }
  171.  
  172. # start the script hidden and lock the close button
  173. [void][Win32.Win32Utils]::ShowWindowAsync($ConsolePtr, 0) # 0 = SW_HIDE
  174. Disable-CloseButton
  175.  
  176. # find the GUID of the "Display brightness"
  177. $BrightGUID = $null
  178. $query = powercfg /q SCHEME_CURRENT SUB_VIDEO
  179. foreach ($line in $query) {
  180.     if ($line -match "GUID:\s+([\w-]+).+\(Display brightness\)") {
  181.         $BrightGUID = $matches[1].Trim(); break
  182.     }
  183. }
  184.  
  185. Write-Log "BrightGUID $BrightGUID"
  186.  
  187. # tray icon
  188.  
  189. Add-Type -AssemblyName System.Windows.Forms, System.Drawing
  190. [System.Windows.Forms.Application]::EnableVisualStyles()
  191. $TrayIcon = New-Object System.Windows.Forms.NotifyIcon
  192. $TrayIcon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon((Get-Process -Id $PID).Path)
  193. $TrayIcon.Visible = $true
  194.  
  195. $ContextMenu = New-Object System.Windows.Forms.ContextMenuStrip
  196. $StatusItem = New-Object System.Windows.Forms.ToolStripMenuItem -Property @{ Enabled = $false; Text = "Plan: Init..." }
  197.  
  198. # dashboard
  199. $ToggleItem = New-Object System.Windows.Forms.ToolStripMenuItem
  200. $ToggleItem.Text = "Show Dashboard"
  201. $ToggleItem.add_Click({
  202.     if ([Win32.Win32Utils]::IsWindowVisible($ConsolePtr)) {
  203.         [void][Win32.Win32Utils]::ShowWindowAsync($ConsolePtr, 0) # Hide
  204.         $ToggleItem.Text = "Show Dashboard"
  205.     } else {
  206.         [void][Win32.Win32Utils]::ShowWindowAsync($ConsolePtr, 5) # Show
  207.         Disable-CloseButton
  208.         $ToggleItem.Text = "Minimize to tray"
  209.     }
  210. })
  211.  
  212. $ExitItem = New-Object System.Windows.Forms.ToolStripMenuItem -Property @{ Text = "Exit" }
  213. $ExitItem.add_Click({ $TrayIcon.Visible = $false; [void](Stop-Process -Id $PID) })
  214.  
  215. [void]$ContextMenu.Items.Add($StatusItem)
  216. [void]$ContextMenu.Items.Add("-")
  217. [void]$ContextMenu.Items.Add($ToggleItem)
  218. [void]$ContextMenu.Items.Add($ExitItem)
  219. $TrayIcon.ContextMenuStrip = $ContextMenu
  220.  
  221. # get currebnt brightness
  222. function Get-CurrentBrightness {
  223.     $bObj = Get-CimInstance -Namespace root/WMI -ClassName WmiMonitorBrightness | Select-Object -First 1
  224.     return $bObj.CurrentBrightness
  225. }
  226.  
  227. # set brightness level for both AC and DC modes for a target plan
  228. function Set-PlanBrightness ($targetGuid, $brightness) {
  229.     if ($null -eq $BrightGUID) { return }
  230.     [void](Start-Process "powercfg.exe" -ArgumentList "/setacvalueindex $targetGuid $DisplaySubGroup $BrightGUID $brightness" -WindowStyle Hidden -Wait)
  231.     [void](Start-Process "powercfg.exe" -ArgumentList "/setdcvalueindex $targetGuid $DisplaySubGroup $BrightGUID $brightness" -WindowStyle Hidden -Wait)
  232. }
  233.  
  234. function Get-IsOnAC {
  235.     $battery = Get-CimInstance -ClassName Win32_Battery -ErrorAction SilentlyContinue
  236.     if ($null -eq $battery) { return $true }
  237.     return $battery.BatteryStatus -eq 2
  238. }
  239.  
  240. function Get-ActivePlanGuid {
  241.     $ptr = [IntPtr]::Zero
  242.     [void][Win32.Win32Utils]::PowerGetActiveScheme([IntPtr]::Zero, [ref]$ptr)
  243.     if ($ptr -ne [IntPtr]::Zero) {
  244.         return ([System.Runtime.InteropServices.Marshal]::PtrToStructure($ptr, [type][Guid])).ToString().ToLower()
  245.     }
  246.     return ""
  247. }
  248.  
  249. # main loop
  250.  
  251. $lastProcessCheck = [DateTime]::MinValue
  252. $lastMode = $null
  253.  
  254. Write-Log "PABS v$PABSversion Online - Monitoring started" "Cyan"
  255.  
  256.  
  257. $ActiveGUID = Get-ActivePlanGuid
  258. $ActiveAlias = "Unknown"
  259.  
  260. foreach ($key in $PowerPlans.Keys) {
  261.     if ($PowerPlans[$key].ToLower() -eq $ActiveGUID.ToLower()) {
  262.         $ActiveAlias = $key
  263.         break
  264.     }
  265. }
  266.  
  267. Write-Log "--- System State ---" "Gray"
  268. Write-Log "Current power plan: $ActiveAlias ($ActiveGUID)" "Cyan"
  269.  
  270. while ($true) {
  271.     [System.Windows.Forms.Application]::DoEvents()
  272.  
  273.     # Ctrl+Shift+B monitoring
  274.     if ([Win32.Win32Utils]::GetAsyncKeyState(17) -band 0x8000) {
  275.         if (([Win32.Win32Utils]::GetAsyncKeyState(16) -band 0x8000) -and ([Win32.Win32Utils]::GetAsyncKeyState(66) -band 0x8000)) {
  276.             $currB = Get-CurrentBrightness
  277.             Write-Log "Syncing brightness: $currB% to ALL defined plans. It may take a while..." "Magenta"
  278.             foreach ($guid in $PowerPlans.Values) { Set-PlanBrightness $guid $currB }
  279.             & powercfg.exe /setactive (Get-ActivePlanGuid)
  280.             Start-Sleep -Milliseconds 500
  281.             Write-Log "Syncing brightness: Finished" "Magenta"
  282.         }
  283.     }
  284.  
  285.     # monitoring and AC/battery logic
  286.     $now = [DateTime]::Now
  287.     if ($now -gt $lastProcessCheck.AddSeconds($ProcessCheckInterval)) {
  288.        
  289.        
  290.         $sw = [System.Diagnostics.Stopwatch]::StartNew()
  291.  
  292.         $IsOnAC = Get-IsOnAC
  293.         $currentModeStr = if($IsOnAC){"AC"}else{"BAT"}
  294.        
  295.        
  296.         $CurrentWL = if($IsOnAC){$WatchListAC}else{$WatchListBattery}
  297.         $TargetAlias = if($IsOnAC){$DefaultPPAC}else{$DefaultPPBattery}
  298.        
  299.         $time = Measure-Command {      
  300.        
  301.             $currentSessionID = [System.Diagnostics.Process]::GetCurrentProcess().SessionId
  302.             $procPaths = Get-CimInstance -ClassName Win32_Process -Filter "SessionId=$currentSessionID" |
  303.              Select-Object -ExpandProperty ExecutablePath | Where-Object { $_ }
  304.        
  305.         }
  306.        
  307.         if ($logCriticalPath -eq 1 ) {
  308.             Write-Log "Get process list: $($time.TotalMilliseconds) ms"
  309.         }
  310.        
  311.         $time = Measure-Command {
  312.        
  313.             $TriggeringPath = "Default"
  314.             :OuterLoop foreach ($item in $CurrentWL) {
  315.                 foreach ($path in $procPaths) {
  316.                     if ($path -like $item.Path) {
  317.                         $TargetAlias = $item.Plan
  318.                         $TriggeringPath = $path
  319.                         break OuterLoop
  320.                     }
  321.                 }
  322.             }
  323.         }
  324.         if ($logCriticalPath -eq 1 ) {
  325.             Write-Log "OuterLoop: $($time.TotalMilliseconds) ms"
  326.         }
  327.        
  328.         $ActiveGUID = Get-ActivePlanGuid
  329.        
  330.        
  331.         if ($logCriticalPath -eq 1) {
  332.             $ActiveAlias = "Unknown"
  333.            
  334.             foreach ($key in $PowerPlans.Keys) {
  335.                 if ($PowerPlans[$key].ToLower() -eq $ActiveGUID.ToLower()) {
  336.                     $ActiveAlias = $key
  337.                     break
  338.                 }
  339.             }
  340.    
  341.             Write-Log "--- System State ---" "Gray"
  342.             Write-Log "Current Active: $ActiveAlias ($ActiveGUID)" "Cyan"
  343.             Write-Log "Target Plan: $TargetAlias" "White"
  344.         }
  345.        
  346.        
  347.        
  348.        
  349.         if ($PowerPlans.ContainsKey($TargetAlias)) {
  350.            
  351.             $TargetGUID = $PowerPlans[$TargetAlias].ToLower()
  352.            
  353.            
  354.             if ($ActiveGUID -ne $TargetGUID -or $currentModeStr -ne $lastMode) {
  355.                 $StatusItem.Text = "Plan: $TargetAlias [$currentModeStr]"
  356.                 $TrayIcon.Text = "PABS: $TargetAlias ($currentModeStr)"
  357.                 $lastMode = $currentModeStr
  358.             }
  359.            
  360.             if ($ActiveGUID -ne $TargetGUID) {
  361.                 # inherit current brightness to the target plan if enabled
  362.                 if ($AutoInheritBrightness -eq 1) {
  363.                     $currB = Get-CurrentBrightness
  364.                     Write-Log "Inheriting brightness: $currB% for $TargetAlias" "Gray"
  365.                     Set-PlanBrightness $TargetGUID $currB
  366.                 }
  367.                 # apply the target power plan
  368.                 & powercfg.exe /setactive $TargetGUID
  369.                 [void](Start-Process "powercfg.exe" -ArgumentList "/setactive $TargetGUID" -WindowStyle Hidden -Wait)
  370.                 Write-Log "Switching to $TargetAlias ($currentModeStr). Triggered by: $TriggeringPath" "Yellow"
  371.             }
  372.            
  373.            
  374.         }
  375.         $lastProcessCheck = $now
  376.        
  377.     }
  378.     Start-Sleep -Milliseconds $KeyCheckInterval
  379. }
Advertisement
Add Comment
Please, Sign In to add comment