11<#
22. SYNOPSIS
3- MonitorControl Pro v3.17 .0 - Advanced Display & GPU Settings Utility
3+ MonitorControl Pro v3.18 .0 - Advanced Display & GPU Settings Utility
44. DESCRIPTION
55 Comprehensive GUI for monitor DDC/CI control with VCP explorer, input switching,
66 color temperature presets, sync across monitors, and time-based automation.
77. NOTES
8- Version: 3.17 .0 - Enhanced with profile bundle export/import
8+ Version: 3.18 .0 - Enhanced with cloud-sync profile storage
99#>
1010
1111param ([switch ]$StartMinimized , [string ]$LoadProfile )
@@ -414,15 +414,32 @@ $script:FpsOverlayText = $null
414414$script :FpsOverlayTimer = $null
415415$script :GpuTimer = $null
416416$script :AutoModeTimer = $null
417- $script :ProfilesPath = " $env: APPDATA \MonitorControlPro"
417+ $script :DefaultProfilesPath = " $env: APPDATA \MonitorControlPro"
418+ $script :ProfileStorageSettingsPath = Join-Path $script :DefaultProfilesPath " profile-storage.json"
419+ $script :ProfilesPath = $script :DefaultProfilesPath
420+ $script :ProfileStorageMode = " Local"
421+ if (-not (Test-Path $script :DefaultProfilesPath )) { New-Item - ItemType Directory - Path $script :DefaultProfilesPath - Force | Out-Null }
422+ if (Test-Path $script :ProfileStorageSettingsPath ) {
423+ try {
424+ $profileStorage = Get-Content - Path $script :ProfileStorageSettingsPath - Raw | ConvertFrom-Json
425+ $configuredPath = [string ]$profileStorage.ProfilePath
426+ if (-not [string ]::IsNullOrWhiteSpace($configuredPath )) {
427+ $script :ProfilesPath = [System.IO.Path ]::GetFullPath([Environment ]::ExpandEnvironmentVariables($configuredPath ))
428+ $script :ProfileStorageMode = if ($profileStorage.Mode ) { [string ]$profileStorage.Mode } else { " Sync" }
429+ }
430+ } catch {
431+ $script :ProfilesPath = $script :DefaultProfilesPath
432+ $script :ProfileStorageMode = " Local"
433+ }
434+ }
418435$script :AppProfileRulesPath = Join-Path $script :ProfilesPath " app-profile-rules.json"
419436$script :ProfileScheduleRulesPath = Join-Path $script :ProfilesPath " profile-schedules.json"
420437$script :IdleDimSettingsPath = Join-Path $script :ProfilesPath " idle-dim.json"
421438$script :BatteryProfileSettingsPath = Join-Path $script :ProfilesPath " battery-profile.json"
422439$script :ProfileSchemaVersion = 2
423440$script :ProfileBundleSchemaVersion = 1
424441$script :ProfileExportsPath = Join-Path $script :ProfilesPath " exports"
425- $script :ProfileMetadataFiles = @ (" app-profile-rules.json" , " profile-schedules.json" , " idle-dim.json" , " battery-profile.json" )
442+ $script :ProfileMetadataFiles = @ (" app-profile-rules.json" , " profile-schedules.json" , " idle-dim.json" , " battery-profile.json" , " profile-storage.json " )
426443$script :UpdatingUI = $false
427444$script :ApplyToAll = $false
428445$script :AutoModeEnabled = $false
@@ -786,11 +803,22 @@ function Get-CpuTemperature {
786803 return $null
787804}
788805
789- if (-not (Test-Path $script :ProfilesPath )) { New-Item - ItemType Directory - Path $script :ProfilesPath - Force | Out-Null }
806+ try {
807+ if (-not (Test-Path $script :ProfilesPath )) { New-Item - ItemType Directory - Path $script :ProfilesPath - Force | Out-Null }
808+ } catch {
809+ $script :ProfilesPath = $script :DefaultProfilesPath
810+ $script :ProfileStorageMode = " Local"
811+ $script :AppProfileRulesPath = Join-Path $script :ProfilesPath " app-profile-rules.json"
812+ $script :ProfileScheduleRulesPath = Join-Path $script :ProfilesPath " profile-schedules.json"
813+ $script :IdleDimSettingsPath = Join-Path $script :ProfilesPath " idle-dim.json"
814+ $script :BatteryProfileSettingsPath = Join-Path $script :ProfilesPath " battery-profile.json"
815+ $script :ProfileExportsPath = Join-Path $script :ProfilesPath " exports"
816+ if (-not (Test-Path $script :ProfilesPath )) { New-Item - ItemType Directory - Path $script :ProfilesPath - Force | Out-Null }
817+ }
790818
791819[xml ]$xaml = @"
792820<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
793- Title="MonitorControl Pro v3.17 .0" Width="640" Height="680" MinWidth="560" MinHeight="560"
821+ Title="MonitorControl Pro v3.18 .0" Width="640" Height="680" MinWidth="560" MinHeight="560"
794822 Background="#0a0a0a" WindowStartupLocation="CenterScreen" ResizeMode="CanResizeWithGrip">
795823<Window.Resources>
796824 <ControlTemplate x:Key="ComboBoxToggleButton" TargetType="ToggleButton">
@@ -924,7 +952,7 @@ if (-not (Test-Path $script:ProfilesPath)) { New-Item -ItemType Directory -Path
924952 <Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
925953 <StackPanel VerticalAlignment="Center">
926954 <TextBlock Text="MonitorControl Pro" FontSize="16" FontWeight="SemiBold" Foreground="#fff" FontFamily="Segoe UI"/>
927- <TextBlock Text="v3.17 .0 - Click monitor to select" FontSize="9" Foreground="#505050" Margin="0,1,0,0"/>
955+ <TextBlock Text="v3.18 .0 - Click monitor to select" FontSize="9" Foreground="#505050" Margin="0,1,0,0"/>
928956 </StackPanel>
929957 <StackPanel Grid.Column="2" Orientation="Horizontal">
930958 <CheckBox x:Name="ApplyAllCheckbox" Content="All Monitors" VerticalAlignment="Center" Margin="0,0,10,0"/>
@@ -1117,7 +1145,7 @@ if (-not (Test-Path $script:ProfilesPath)) { New-Item -ItemType Directory -Path
11171145 </TabItem>
11181146 <TabItem Header="Profiles">
11191147 <Border Background="#151515" CornerRadius="0,5,5,5" Padding="10"><Grid>
1120- <Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="8"/><RowDefinition Height="*"/><RowDefinition Height="8"/><RowDefinition Height="Auto"/><RowDefinition Height="6"/><RowDefinition Height="Auto"/><RowDefinition Height="8"/><RowDefinition Height="Auto"/></Grid.RowDefinitions>
1148+ <Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="8"/><RowDefinition Height="*"/><RowDefinition Height="8"/><RowDefinition Height="Auto"/><RowDefinition Height="6"/><RowDefinition Height="Auto"/><RowDefinition Height="8"/><RowDefinition Height="Auto"/><RowDefinition Height="8"/><RowDefinition Height="Auto"/>< /Grid.RowDefinitions>
11211149 <Border Background="#1a1a1a" CornerRadius="5" Padding="10"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="6"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
11221150 <TextBox x:Name="ProfileNameBox" Text="My Profile"/>
11231151 <Button x:Name="SaveProfileBtn" Grid.Column="2" Content="Save" Style="{StaticResource GreenBtn}" Padding="10,4"/>
@@ -1135,6 +1163,13 @@ if (-not (Test-Path $script:ProfilesPath)) { New-Item -ItemType Directory -Path
11351163 <Button x:Name="ImportProfilesBtn" Grid.Column="2" Content="Import Bundle" Style="{StaticResource Btn}"/>
11361164 </Grid>
11371165 <Border Grid.Row="8" Background="#1a1a1a" CornerRadius="5" Padding="10"><Grid>
1166+ <Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="6"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="6"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
1167+ <StackPanel><TextBlock Text="Profile Storage" FontSize="10" Foreground="#909090"/>
1168+ <TextBlock x:Name="ProfileStorageStatusText" Text="Local" FontSize="8" Foreground="#707070" Margin="0,2,0,0" TextTrimming="CharacterEllipsis"/></StackPanel>
1169+ <Button x:Name="ProfileSyncFolderBtn" Grid.Column="2" Content="Sync Folder" Style="{StaticResource Btn}" Padding="8,4" FontSize="9"/>
1170+ <Button x:Name="ProfileLocalFolderBtn" Grid.Column="4" Content="Use Local" Style="{StaticResource Btn}" Padding="8,4" FontSize="9"/>
1171+ </Grid></Border>
1172+ <Border Grid.Row="10" Background="#1a1a1a" CornerRadius="5" Padding="10"><Grid>
11381173 <Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="6"/><RowDefinition Height="Auto"/><RowDefinition Height="6"/><RowDefinition Height="Auto"/><RowDefinition Height="6"/><RowDefinition Height="Auto"/></Grid.RowDefinitions>
11391174 <Grid><CheckBox x:Name="AppProfileEnabledCheckbox" Content="Per-application profiles" VerticalAlignment="Center"/>
11401175 <TextBlock x:Name="AppProfileStatusText" Text="Off" FontSize="9" Foreground="#707070" HorizontalAlignment="Right" VerticalAlignment="Center"/></Grid>
@@ -1274,6 +1309,7 @@ $vcpResultBox = $window.FindName("VCPResultBox"); $vcpSetValueBox = $window.Find
12741309$profileNameBox = $window.FindName (" ProfileNameBox" ); $profilesList = $window.FindName (" ProfilesList" )
12751310$saveProfileBtn = $window.FindName (" SaveProfileBtn" ); $loadProfileBtn = $window.FindName (" LoadProfileBtn" ); $deleteProfileBtn = $window.FindName (" DeleteProfileBtn" )
12761311$exportProfilesBtn = $window.FindName (" ExportProfilesBtn" ); $importProfilesBtn = $window.FindName (" ImportProfilesBtn" )
1312+ $profileStorageStatusText = $window.FindName (" ProfileStorageStatusText" ); $profileSyncFolderBtn = $window.FindName (" ProfileSyncFolderBtn" ); $profileLocalFolderBtn = $window.FindName (" ProfileLocalFolderBtn" )
12771313$appProfileEnabledCheckbox = $window.FindName (" AppProfileEnabledCheckbox" ); $appProfileStatusText = $window.FindName (" AppProfileStatusText" )
12781314$appProfileExeBox = $window.FindName (" AppProfileExeBox" ); $appProfileCaptureBtn = $window.FindName (" AppProfileCaptureBtn" )
12791315$appProfileProfileCombo = $window.FindName (" AppProfileProfileCombo" ); $appProfileAddBtn = $window.FindName (" AppProfileAddBtn" )
@@ -1549,6 +1585,86 @@ function Import-ProfileBundle {
15491585 }
15501586}
15511587
1588+ function Set-ProfileStorageRoot {
1589+ param ([string ]$Path , [string ]$Mode = " Sync" )
1590+ if ([string ]::IsNullOrWhiteSpace($Path )) {
1591+ Update-Status " Profile storage path is empty"
1592+ return $false
1593+ }
1594+ try {
1595+ $expandedPath = [Environment ]::ExpandEnvironmentVariables($Path )
1596+ $fullPath = [System.IO.Path ]::GetFullPath($expandedPath )
1597+ if (-not (Test-Path $fullPath )) { New-Item - ItemType Directory - Path $fullPath - Force | Out-Null }
1598+ $script :ProfilesPath = $fullPath
1599+ $script :ProfileStorageMode = $Mode
1600+ $script :AppProfileRulesPath = Join-Path $script :ProfilesPath " app-profile-rules.json"
1601+ $script :ProfileScheduleRulesPath = Join-Path $script :ProfilesPath " profile-schedules.json"
1602+ $script :IdleDimSettingsPath = Join-Path $script :ProfilesPath " idle-dim.json"
1603+ $script :BatteryProfileSettingsPath = Join-Path $script :ProfilesPath " battery-profile.json"
1604+ $script :ProfileExportsPath = Join-Path $script :ProfilesPath " exports"
1605+ return $true
1606+ } catch {
1607+ Update-Status " Profile storage failed: $ ( $_.Exception.Message ) "
1608+ return $false
1609+ }
1610+ }
1611+
1612+ function Save-ProfileStorageSettings {
1613+ if (-not (Test-Path $script :DefaultProfilesPath )) { New-Item - ItemType Directory - Path $script :DefaultProfilesPath - Force | Out-Null }
1614+ $payload = [PSCustomObject ]@ {
1615+ Mode = $script :ProfileStorageMode
1616+ ProfilePath = $script :ProfilesPath
1617+ UpdatedAt = (Get-Date ).ToString(" o" )
1618+ }
1619+ $payload | ConvertTo-Json | Set-Content - Path $script :ProfileStorageSettingsPath - Encoding UTF8
1620+ }
1621+
1622+ function Reset-ProfileBackedAutomationState {
1623+ $script :AppProfileEnabled = $false
1624+ $script :AppProfileRules = @ ()
1625+ $script :LastForegroundExe = $null
1626+ $script :LastAppliedAppProfileKey = $null
1627+ $script :ProfileScheduleEnabled = $false
1628+ $script :ProfileSchedules = @ ()
1629+ $script :LastAppliedScheduleKey = $null
1630+ $script :IdleDimEnabled = $false
1631+ $script :IdleDimMinutes = 10
1632+ $script :IdleDimBrightness = 20
1633+ $script :IdleDimRestoreOnActivity = $true
1634+ $script :IdleDimActive = $false
1635+ $script :IdleDimPreviousBrightness = $null
1636+ $script :BatteryProfileEnabled = $false
1637+ $script :BatteryBrightness = 35
1638+ $script :AcBrightness = 75
1639+ $script :LastPowerLineStatus = $null
1640+ $script :ProfileCycleIndex = -1
1641+ }
1642+
1643+ function Update-ProfileStorageControls {
1644+ if ($null -eq $profileStorageStatusText ) { return }
1645+ $mode = if ($script :ProfileStorageMode -eq " Local" ) { " Local" } else { " Sync" }
1646+ $profileStorageStatusText.Text = " $mode - $script :ProfilesPath "
1647+ $profileStorageStatusText.ToolTip = $script :ProfilesPath
1648+ }
1649+
1650+ function Reload-ProfileStorageState {
1651+ Reset-ProfileBackedAutomationState
1652+ Load- AppProfileRules
1653+ Load- ProfileSchedules
1654+ Load- IdleDimSettings
1655+ Load- BatteryProfileSettings
1656+ Update-ProfilesList
1657+ Update-AppProfileControls
1658+ Update-ScheduleControls
1659+ Update-IdleDimControls
1660+ Update-BatteryProfileControls
1661+ Start-AppProfileWatcher
1662+ Start-ProfileScheduleWatcher
1663+ Start-IdleDimWatcher
1664+ Start-BatteryProfileWatcher
1665+ Update-ProfileStorageControls
1666+ }
1667+
15521668function Normalize-AppExeName {
15531669 param ([string ]$ExeName )
15541670 $name = $ExeName.Trim ()
@@ -2470,6 +2586,26 @@ $importProfilesBtn.Add_Click({
24702586 Import-ProfileBundle - BundlePath $dialog.FileName | Out-Null
24712587 }
24722588})
2589+ $profileSyncFolderBtn.Add_Click ({
2590+ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
2591+ $dialog.Description = " Select a OneDrive or Dropbox folder for MonitorControl profiles"
2592+ $dialog.ShowNewFolderButton = $true
2593+ $dialog.SelectedPath = if (Test-Path $script :ProfilesPath ) { $script :ProfilesPath } else { $script :DefaultProfilesPath }
2594+ if ($dialog.ShowDialog () -eq [System.Windows.Forms.DialogResult ]::OK) {
2595+ if (Set-ProfileStorageRoot - Path $dialog.SelectedPath - Mode " Sync" ) {
2596+ Save-ProfileStorageSettings
2597+ Reload- ProfileStorageState
2598+ Update-Status " Profile sync folder: $ ( Split-Path - Path $script :ProfilesPath - Leaf) "
2599+ }
2600+ }
2601+ })
2602+ $profileLocalFolderBtn.Add_Click ({
2603+ if (Set-ProfileStorageRoot - Path $script :DefaultProfilesPath - Mode " Local" ) {
2604+ Save-ProfileStorageSettings
2605+ Reload- ProfileStorageState
2606+ Update-Status " Profile storage: local"
2607+ }
2608+ })
24732609
24742610$appProfileEnabledCheckbox.Add_Checked ({
24752611 if ($script :UpdatingAppProfileUI ) { return }
@@ -2650,6 +2786,7 @@ Load-AppProfileRules; Update-AppProfileControls; Start-AppProfileWatcher
26502786Load- ProfileSchedules; Update-ScheduleControls ; Start-ProfileScheduleWatcher
26512787Load- IdleDimSettings; Update-IdleDimControls ; Start-IdleDimWatcher
26522788Load- BatteryProfileSettings; Update-BatteryProfileControls ; Start-BatteryProfileWatcher
2789+ Update-ProfileStorageControls
26532790if (-not ($script :HasNvidia -or $script :HasAmd -or $script :HasCpuTempMonitor )) { $gpuTab.Visibility = " Collapsed" } else {
26542791 $script :GpuTimer = New-Object System.Windows.Threading.DispatcherTimer; $script :GpuTimer.Interval = [TimeSpan ]::FromSeconds(2 )
26552792 $script :GpuTimer.Add_Tick ({ Update-GpuStats }); $script :GpuTimer.Start (); Update-GpuStats
0 commit comments