Nimbi

Windows-11-Deskthemepack-Compiler.ps1

Jul 19th, 2026 (edited)
29
0
Never
3
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. Add-Type -AssemblyName System.Windows.Forms
  2. Add-Type -AssemblyName System.Drawing
  3.  
  4. # Detect Windows System Dark Mode from Registry
  5. $RegPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"
  6. $IsDarkMode = $true
  7. if (Test-Path $RegPath) {
  8.     if ((Get-ItemProperty -Path $RegPath).AppsUseLightTheme -eq 1) { $IsDarkMode = $false }
  9. }
  10.  
  11. # Define Color Matrix based on Dark/Light mode status
  12. if ($IsDarkMode) {
  13.     $ColorBg = [System.Drawing.Color]::FromArgb(32, 32, 32)
  14.     $ColorText = [System.Drawing.Color]::White
  15.     $ColorInputBg = [System.Drawing.Color]::FromArgb(45, 45, 45)
  16.     $ColorGrpBg = [System.Drawing.Color]::FromArgb(40, 40, 40)
  17. } else {
  18.     $ColorBg = [System.Drawing.Color]::FromArgb(243, 243, 243)
  19.     $ColorText = [System.Drawing.Color]::Black
  20.     $ColorInputBg = [System.Drawing.Color]::White
  21.     $ColorGrpBg = [System.Drawing.Color]::FromArgb(255, 255, 255)
  22. }
  23.  
  24. # ----------------------------------------------------
  25. # VISUAL FORM SETUP
  26. # ----------------------------------------------------
  27. $Form = New-Object System.Windows.Forms.Form
  28. $Form.Text = "Windows 11 Deskthemepack Compiler"
  29. $Form.Size = New-Object System.Drawing.Size(520, 660)
  30. $Form.StartPosition = "CenterScreen"
  31. $Form.FormBorderStyle = "FixedDialog"
  32. $Form.MaximizeBox = $false
  33. $Form.BackColor = $ColorBg
  34.  
  35. $FontLabel = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
  36. $FontInput = New-Object System.Drawing.Font("Segoe UI", 9)
  37.  
  38. # Theme Name Box
  39. $LblName = New-Object System.Windows.Forms.Label
  40. $LblName.Text = "Theme Name:"
  41. $LblName.Location = New-Object System.Drawing.Point(20, 20)
  42. $LblName.Size = New-Object System.Drawing.Size(120, 20)
  43. $LblName.Font = $FontLabel
  44. $LblName.ForeColor = $ColorText
  45. $Form.Controls.Add($LblName)
  46.  
  47. $TxtName = New-Object System.Windows.Forms.TextBox
  48. $TxtName.Location = New-Object System.Drawing.Point(150, 18)
  49. $TxtName.Size = New-Object System.Drawing.Size(330, 20)
  50. $TxtName.Font = $FontInput
  51. $TxtName.BackColor = $ColorInputBg
  52. $TxtName.ForeColor = $ColorText
  53. $TxtName.Text = "MyCustomTheme"
  54. $Form.Controls.Add($TxtName)
  55.  
  56. # Setup input boxes with global scope
  57. $Global:TxtWallBox = New-Object System.Windows.Forms.TextBox
  58. $Global:TxtCursBox = New-Object System.Windows.Forms.TextBox
  59. $Global:TxtSndBox  = New-Object System.Windows.Forms.TextBox
  60.  
  61. # Row 1: Wallpapers
  62. $LblW = New-Object System.Windows.Forms.Label
  63. $LblW.Text = "Wallpapers Folder:"
  64. $LblW.Location = New-Object System.Drawing.Point(20, 65)
  65. $LblW.Size = New-Object System.Drawing.Size(120, 20)
  66. $LblW.Font = $FontLabel
  67. $LblW.ForeColor = $ColorText
  68. $Form.Controls.Add($LblW)
  69.  
  70. $Global:TxtWallBox.Location = New-Object System.Drawing.Point(150, 63)
  71. $Global:TxtWallBox.Size = New-Object System.Drawing.Size(240, 20)
  72. $Global:TxtWallBox.ReadOnly = $true
  73. $Global:TxtWallBox.Font = $FontInput
  74. $Global:TxtWallBox.BackColor = $ColorInputBg
  75. $Global:TxtWallBox.ForeColor = $ColorText
  76. $Form.Controls.Add($Global:TxtWallBox)
  77.  
  78. $BtnW = New-Object System.Windows.Forms.Button
  79. $BtnW.Text = "Browse..."
  80. $BtnW.Location = New-Object System.Drawing.Point(400, 61)
  81. $BtnW.Size = New-Object System.Drawing.Size(80, 25)
  82. $BtnW.Font = $FontInput
  83. $BtnW.BackColor = $ColorInputBg
  84. $BtnW.ForeColor = $ColorText
  85. $BtnW.Add_Click({
  86.     $FB = New-Object System.Windows.Forms.FolderBrowserDialog
  87.     if ($FB.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $Global:TxtWallBox.Text = $FB.SelectedPath }
  88. })
  89. $Form.Controls.Add($BtnW)
  90.  
  91. # Row 2: Cursors
  92. $LblC = New-Object System.Windows.Forms.Label
  93. $LblC.Text = "Cursors Folder:"
  94. $LblC.Location = New-Object System.Drawing.Point(20, 110)
  95. $LblC.Size = New-Object System.Drawing.Size(120, 20)
  96. $LblC.Font = $FontLabel
  97. $LblC.ForeColor = $ColorText
  98. $Form.Controls.Add($LblC)
  99.  
  100. $Global:TxtCursBox.Location = New-Object System.Drawing.Point(150, 108)
  101. $Global:TxtCursBox.Size = New-Object System.Drawing.Size(240, 20)
  102. $Global:TxtCursBox.ReadOnly = $true
  103. $Global:TxtCursBox.Font = $FontInput
  104. $Global:TxtCursBox.BackColor = $ColorInputBg
  105. $Global:TxtCursBox.ForeColor = $ColorText
  106. $Form.Controls.Add($Global:TxtCursBox)
  107.  
  108. $BtnC = New-Object System.Windows.Forms.Button
  109. $BtnC.Text = "Browse..."
  110. $BtnC.Location = New-Object System.Drawing.Point(400, 106)
  111. $BtnC.Size = New-Object System.Drawing.Size(80, 25)
  112. $BtnC.Font = $FontInput
  113. $BtnC.BackColor = $ColorInputBg
  114. $BtnC.ForeColor = $ColorText
  115. $BtnC.Add_Click({
  116.     $FB = New-Object System.Windows.Forms.FolderBrowserDialog
  117.     if ($FB.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $Global:TxtCursBox.Text = $FB.SelectedPath }
  118. })
  119. $Form.Controls.Add($BtnC)
  120.  
  121. # Row 3: Sounds
  122. $LblS = New-Object System.Windows.Forms.Label
  123. $LblS.Text = "Sounds Folder:"
  124. $LblS.Location = New-Object System.Drawing.Point(20, 155)
  125. $LblS.Size = New-Object System.Drawing.Size(120, 20)
  126. $LblS.Font = $FontLabel
  127. $LblS.ForeColor = $ColorText
  128. $Form.Controls.Add($LblS)
  129.  
  130. $Global:TxtSndBox.Location = New-Object System.Drawing.Point(150, 153)
  131. $Global:TxtSndBox.Size = New-Object System.Drawing.Size(240, 20)
  132. $Global:TxtSndBox.ReadOnly = $true
  133. $Global:TxtSndBox.Font = $FontInput
  134. $Global:TxtSndBox.BackColor = $ColorInputBg
  135. $Global:TxtSndBox.ForeColor = $ColorText
  136. $Form.Controls.Add($Global:TxtSndBox)
  137.  
  138. $BtnS = New-Object System.Windows.Forms.Button
  139. $BtnS.Text = "Browse..."
  140. $BtnS.Location = New-Object System.Drawing.Point(400, 151)
  141. $BtnS.Size = New-Object System.Drawing.Size(80, 25)
  142. $BtnS.Font = $FontInput
  143. $BtnS.BackColor = $ColorInputBg
  144. $BtnS.ForeColor = $ColorText
  145. $BtnS.Add_Click({
  146.     $FB = New-Object System.Windows.Forms.FolderBrowserDialog
  147.     if ($FB.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $Global:TxtSndBox.Text = $FB.SelectedPath }
  148. })
  149. $Form.Controls.Add($BtnS)
  150.  
  151. # Configurations Box Frame
  152. $GrpPersonal = New-Object System.Windows.Forms.GroupBox
  153. $GrpPersonal.Text = "Personalization Configurations"
  154. $GrpPersonal.Location = New-Object System.Drawing.Point(20, 205)
  155. $GrpPersonal.Size = New-Object System.Drawing.Size(460, 310)
  156. $GrpPersonal.Font = $FontLabel
  157. $GrpPersonal.BackColor = $ColorGrpBg
  158. $GrpPersonal.ForeColor = $ColorText
  159.  
  160. # --- PANEL 1: Isolated System Mode Radio Group ---
  161. $PnlSys = New-Object System.Windows.Forms.Panel
  162. $PnlSys.Location = New-Object System.Drawing.Point(10, 25)
  163. $PnlSys.Size = New-Object System.Drawing.Size(440, 30)
  164. $GrpPersonal.Controls.Add($PnlSys)
  165.  
  166. $LblSysMode = New-Object System.Windows.Forms.Label
  167. $LblSysMode.Text = "System Mode:"
  168. $LblSysMode.Location = New-Object System.Drawing.Point(10, 5)
  169. $LblSysMode.Size = New-Object System.Drawing.Size(100, 20)
  170. $LblSysMode.ForeColor = $ColorText
  171. $PnlSys.Controls.Add($LblSysMode)
  172.  
  173. $RadSysDark = New-Object System.Windows.Forms.RadioButton
  174. $RadSysDark.Text = "Dark"
  175. $RadSysDark.Location = New-Object System.Drawing.Point(120, 3)
  176. $RadSysDark.Size = New-Object System.Drawing.Size(65, 20)
  177. $RadSysDark.Checked = $true
  178. $RadSysDark.Font = $FontInput
  179. $PnlSys.Controls.Add($RadSysDark)
  180.  
  181. $RadSysLight = New-Object System.Windows.Forms.RadioButton
  182. $RadSysLight.Text = "Light"
  183. $RadSysLight.Location = New-Object System.Drawing.Point(200, 3)
  184. $RadSysLight.Size = New-Object System.Drawing.Size(65, 20)
  185. $RadSysLight.Font = $FontInput
  186. $PnlSys.Controls.Add($RadSysLight)
  187.  
  188. # --- PANEL 2: Isolated App Mode Radio Group ---
  189. $PnlApp = New-Object System.Windows.Forms.Panel
  190. $PnlApp.Location = New-Object System.Drawing.Point(10, 60)
  191. $PnlApp.Size = New-Object System.Drawing.Size(440, 30)
  192. $GrpPersonal.Controls.Add($PnlApp)
  193.  
  194. $LblAppMode = New-Object System.Windows.Forms.Label
  195. $LblAppMode.Text = "App Mode:"
  196. $LblAppMode.Location = New-Object System.Drawing.Point(10, 5)
  197. $LblAppMode.Size = New-Object System.Drawing.Size(100, 20)
  198. $LblAppMode.ForeColor = $ColorText
  199. $PnlApp.Controls.Add($LblAppMode)
  200.  
  201. $RadAppDark = New-Object System.Windows.Forms.RadioButton
  202. $RadAppDark.Text = "Dark"
  203. $RadAppDark.Location = New-Object System.Drawing.Point(120, 3)
  204. $RadAppDark.Size = New-Object System.Drawing.Size(65, 20)
  205. $RadAppDark.Checked = $true
  206. $RadAppDark.Font = $FontInput
  207. $PnlApp.Controls.Add($RadAppDark)
  208.  
  209. $RadAppLight = New-Object System.Windows.Forms.RadioButton
  210. $RadAppLight.Text = "Light"
  211. $RadAppLight.Location = New-Object System.Drawing.Point(200, 3)
  212. $RadAppLight.Size = New-Object System.Drawing.Size(65, 20)
  213. $RadAppLight.Font = $FontInput
  214. $PnlApp.Controls.Add($RadAppLight)
  215.  
  216. # --- PANEL 3: Isolated Accent Color Radio Group ---
  217. $PnlColor = New-Object System.Windows.Forms.Panel
  218. $PnlColor.Location = New-Object System.Drawing.Point(10, 95)
  219. $PnlColor.Size = New-Object System.Drawing.Size(440, 95)
  220. $GrpPersonal.Controls.Add($PnlColor)
  221.  
  222. $LblColor = New-Object System.Windows.Forms.Label
  223. $LblColor.Text = "Accent Color:"
  224. $LblColor.Location = New-Object System.Drawing.Point(10, 10)
  225. $LblColor.Size = New-Object System.Drawing.Size(100, 20)
  226. $LblColor.ForeColor = $ColorText
  227. $PnlColor.Controls.Add($LblColor)
  228.  
  229. $RadColorAuto = New-Object System.Windows.Forms.RadioButton
  230. $RadColorAuto.Text = "Auto Colorization (Dynamic System Pick)"
  231. $RadColorAuto.Location = New-Object System.Drawing.Point(120, 8)
  232. $RadColorAuto.Size = New-Object System.Drawing.Size(300, 20)
  233. $RadColorAuto.Checked = $true
  234. $RadColorAuto.Font = $FontInput
  235. $PnlColor.Controls.Add($RadColorAuto)
  236.  
  237. $RadColorBest = New-Object System.Windows.Forms.RadioButton
  238. $RadColorBest.Text = "Choose Best Color (Extract from Wallpaper)"
  239. $RadColorBest.Location = New-Object System.Drawing.Point(120, 35)
  240. $RadColorBest.Size = New-Object System.Drawing.Size(300, 20)
  241. $RadColorBest.Font = $FontInput
  242. $PnlColor.Controls.Add($RadColorBest)
  243.  
  244. # --- PANEL 4: Slideshow Toggle & Intervals ---
  245. $PnlSlide = New-Object System.Windows.Forms.Panel
  246. $PnlSlide.Location = New-Object System.Drawing.Point(10, 195)
  247. $PnlSlide.Size = New-Object System.Drawing.Size(440, 100)
  248. $GrpPersonal.Controls.Add($PnlSlide)
  249.  
  250. $ChkSlideshow = New-Object System.Windows.Forms.CheckBox
  251. $ChkSlideshow.Text = "Package all as Slideshow"
  252. $ChkSlideshow.Location = New-Object System.Drawing.Point(10, 5)
  253. $ChkSlideshow.Size = New-Object System.Drawing.Size(200, 20)
  254. $ChkSlideshow.ForeColor = $ColorText
  255. $ChkSlideshow.Font = $FontLabel
  256. $PnlSlide.Controls.Add($ChkSlideshow)
  257.  
  258. $PnlInterval = New-Object System.Windows.Forms.Panel
  259. $PnlInterval.Location = New-Object System.Drawing.Point(30, 30)
  260. $PnlInterval.Size = New-Object System.Drawing.Size(400, 65)
  261. $PnlInterval.Enabled = $false
  262. $PnlSlide.Controls.Add($PnlInterval)
  263.  
  264. $ChkSlideshow.Add_CheckedChanged({
  265.     $PnlInterval.Enabled = $ChkSlideshow.Checked
  266. })
  267.  
  268. $LblInterval = New-Object System.Windows.Forms.Label
  269. $LblInterval.Text = "Change picture every:"
  270. $LblInterval.Location = New-Object System.Drawing.Point(0, 5)
  271. $LblInterval.Size = New-Object System.Drawing.Size(150, 20)
  272. $LblInterval.ForeColor = $ColorText
  273. $PnlInterval.Controls.Add($LblInterval)
  274.  
  275. $Rad30m = New-Object System.Windows.Forms.RadioButton
  276. $Rad30m.Text = "30 Min"; $Rad30m.Location = New-Object System.Drawing.Point(0, 25); $Rad30m.Size = New-Object System.Drawing.Size(70, 20); $Rad30m.Checked = $true; $Rad30m.Font = $FontInput
  277. $PnlInterval.Controls.Add($Rad30m)
  278.  
  279. $Rad1h = New-Object System.Windows.Forms.RadioButton
  280. $Rad1h.Text = "1 Hr"; $Rad1h.Location = New-Object System.Drawing.Point(75, 25); $Rad1h.Size = New-Object System.Drawing.Size(60, 20); $Rad1h.Font = $FontInput
  281. $PnlInterval.Controls.Add($Rad1h)
  282.  
  283. $Rad3h = New-Object System.Windows.Forms.RadioButton
  284. $Rad3h.Text = "3 Hr"; $Rad3h.Location = New-Object System.Drawing.Point(140, 25); $Rad3h.Size = New-Object System.Drawing.Size(60, 20); $Rad3h.Font = $FontInput
  285. $PnlInterval.Controls.Add($Rad3h)
  286.  
  287. $Rad6h = New-Object System.Windows.Forms.RadioButton
  288. $Rad6h.Text = "6 Hr"; $Rad6h.Location = New-Object System.Drawing.Point(205, 25); $Rad6h.Size = New-Object System.Drawing.Size(60, 20); $Rad6h.Font = $FontInput
  289. $PnlInterval.Controls.Add($Rad6h)
  290.  
  291. $Rad12h = New-Object System.Windows.Forms.RadioButton
  292. $Rad12h.Text = "12 Hr"; $Rad12h.Location = New-Object System.Drawing.Point(270, 25); $Rad12h.Size = New-Object System.Drawing.Size(70, 20); $Rad12h.Font = $FontInput
  293. $PnlInterval.Controls.Add($Rad12h)
  294.  
  295. $Form.Controls.Add($GrpPersonal)
  296.  
  297. $BtnBuild = New-Object System.Windows.Forms.Button
  298. $BtnBuild.Text = "Build Theme Pack"
  299. $BtnBuild.Location = New-Object System.Drawing.Point(20, 540)
  300. $BtnBuild.Size = New-Object System.Drawing.Size(460, 45)
  301. $BtnBuild.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold)
  302. $BtnBuild.BackColor = [System.Drawing.Color]::FromArgb(0, 120, 215)
  303. $BtnBuild.ForeColor = [System.Drawing.Color]::White
  304.  
  305. # ----------------------------------------------------
  306. # COMPILER CORE ARCHITECTURE
  307. # ----------------------------------------------------
  308. $BtnBuild.Add_Click({
  309.     if ([string]::IsNullOrEmpty($TxtName.Text)) {
  310.         [System.Windows.Forms.MessageBox]::Show("Please specify a valid theme name.", "Input Missing")
  311.         return
  312.     }
  313.    
  314.     # Clean incoming directory path strings to completely eliminate trailing backslash crash bugs
  315.     $WallPath = $Global:TxtWallBox.Text.TrimEnd('\')
  316.     $CursPath = $Global:TxtCursBox.Text.TrimEnd('\')
  317.     $SndPath  = $Global:TxtSndBox.Text.TrimEnd('\')
  318.  
  319.     if ([string]::IsNullOrEmpty($WallPath) -or !(Test-Path $WallPath)) {
  320.         [System.Windows.Forms.MessageBox]::Show("A valid wallpaper source directory must be specified.", "Input Missing")
  321.         return
  322.     }
  323.  
  324.     $CleanThemeName = $TxtName.Text -replace '[\\/:*?"<>|]', ''
  325.     $StagingDir = Join-Path $env:TEMP "ThemePackStaging_$(Get-Random)"
  326.     New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null
  327.  
  328.     $ThemeFile = Join-Path $StagingDir "$CleanThemeName.theme"
  329.    
  330.     @("[Theme]", "DisplayName=$CleanThemeName", "") | Out-File $ThemeFile -Encoding UTF8
  331.     @("[MasterThemeSelector]", "MTSM=RJSPBS", "") | Out-File $ThemeFile -Encoding UTF8 -Append
  332.  
  333.     $SysValue = if ($RadSysDark.Checked) { "Dark" } else { "Light" }
  334.     $AppValue = if ($RadAppDark.Checked) { "Dark" } else { "Light" }
  335.     $AutoColorValue = if ($RadColorAuto.Checked) { 1 } else { 0 }
  336.     $HexColorValue = "0XFF4B71B3"
  337.  
  338.     $CabinetFilesList = @()
  339.  
  340.     # 1. Wallpaper Path Configurations
  341.     $Wallpapers = Get-ChildItem -Path $WallPath -File | Where-Object { $_.Extension -match '^\.(jpg|jpeg|png)$' }
  342.     if ($Wallpapers.Count -gt 0) {
  343.         $FirstWall = $Wallpapers[0]
  344.        
  345.         if ($RadColorBest.Checked) {
  346.             try {
  347.                 $Bitmap = New-Object System.Drawing.Bitmap($FirstWall.FullName)
  348.                 $SamplePixel = $Bitmap.GetPixel([int]($Bitmap.Width / 2), [int]($Bitmap.Height / 2))
  349.                 $HexColorValue = "0XFF" + $SamplePixel.R.ToString("X2") + $SamplePixel.G.ToString("X2") + $SamplePixel.B.ToString("X2")
  350.                 $Bitmap.Dispose()
  351.             } catch { $AutoColorValue = 1 }
  352.         }
  353.  
  354.         if ($ChkSlideshow.Checked -and $Wallpapers.Count -gt 1) {
  355.             # Package ALL wallpapers
  356.             foreach ($Wall in $Wallpapers) {
  357.                 $CabinetFilesList += New-Object PSObject -Property @{ SourcePath = $Wall.FullName; CabinetSubdir = "DesktopBackground" }
  358.             }
  359.            
  360.             @("[Control Panel\Desktop]", "Wallpaper=DesktopBackground\$($FirstWall.Name)", "TileWallpaper=0", "WallpaperStyle=10", "MultimonBackgrounds=0", "PicturePosition=4", "") | Out-File $ThemeFile -Encoding UTF8 -Append
  361.            
  362.             # Map selected interval (milliseconds)
  363.             $IntervalValue = 1800000 # default 30 min
  364.             if ($Rad1h.Checked) { $IntervalValue = 3600000 }
  365.             elseif ($Rad3h.Checked) { $IntervalValue = 10800000 }
  366.             elseif ($Rad6h.Checked) { $IntervalValue = 21600000 }
  367.             elseif ($Rad12h.Checked) { $IntervalValue = 43200000 }
  368.            
  369.             @("[Slideshow]", "Interval=$IntervalValue", "Shuffle=1", "ImagesRootPath=DesktopBackground\", "") | Out-File $ThemeFile -Encoding UTF8 -Append
  370.         } else {
  371.             # Single Wallpaper
  372.             $CabinetFilesList += New-Object PSObject -Property @{ SourcePath = $FirstWall.FullName; CabinetSubdir = "DesktopBackground" }
  373.             @("[Control Panel\Desktop]", "Wallpaper=DesktopBackground\$($FirstWall.Name)", "TileWallpaper=0", "WallpaperStyle=10", "") | Out-File $ThemeFile -Encoding UTF8 -Append
  374.         }
  375.     }
  376.  
  377.     # 2. Cursor Configuration Profile Keys
  378.     @("[Control Panel\Cursors]") | Out-File $ThemeFile -Encoding UTF8 -Append
  379.     if (![string]::IsNullOrEmpty($CursPath) -and (Test-Path $CursPath)) {
  380.         $CursorsList = Get-ChildItem -Path $CursPath -File | Where-Object { $_.Extension -match '^\.(cur|ani)$' }
  381.        
  382.         # Use an ordered dictionary to prioritize longer/specific matches (like uparrow) before generic ones (like arrow)
  383.         $CursorMap = [ordered]@{
  384.             "appstarting" = "AppStarting"; "working" = "AppStarting"; "background" = "AppStarting";
  385.             "uparrow" = "UpArrow"; "up" = "UpArrow";
  386.             "crosshair" = "Crosshair"; "cross" = "Crosshair";
  387.             "ibeam" = "IBeam"; "text" = "IBeam";
  388.             "nwpen" = "NWPen"; "pen" = "NWPen";
  389.             "sizenwse" = "SizeNWSE"; "sznwse" = "SizeNWSE";
  390.             "sizenesw" = "SizeNESW"; "sznesw" = "SizeNESW";
  391.             "sizeall" = "SizeAll"; "move" = "SizeAll";
  392.             "sizens" = "SizeNS"; "vertical" = "SizeNS";
  393.             "sizewe" = "SizeWE"; "horizontal" = "SizeWE";
  394.             "unavailable" = "No"; "no" = "No";
  395.             "wait" = "Wait"; "busy" = "Wait"; "loading" = "Wait";
  396.             "help" = "Help";
  397.             "hand" = "Hand"; "link" = "Hand";
  398.             "pin" = "Pin";
  399.             "arrow" = "Arrow"
  400.         }
  401.        
  402.         foreach ($CurFile in $CursorsList) {
  403.             $LowerName = $CurFile.BaseName.ToLower()
  404.             foreach ($Key in $CursorMap.Keys) {
  405.                 if ($LowerName.Contains($Key)) {
  406.                     "$($CursorMap[$Key])=$($CurFile.Name)" | Out-File $ThemeFile -Encoding UTF8 -Append
  407.                     $CabinetFilesList += New-Object PSObject -Property @{ SourcePath = $CurFile.FullName; CabinetSubdir = "" }
  408.                     break
  409.                 }
  410.             }
  411.         }
  412.     }
  413.     @("") | Out-File $ThemeFile -Encoding UTF8 -Append
  414.  
  415.     # 3. Sound Audio Configurations
  416.     @("[Sounds]", "SchemeName=$CleanThemeName Sounds", "") | Out-File $ThemeFile -Encoding UTF8 -Append
  417.     if (![string]::IsNullOrEmpty($SndPath) -and (Test-Path $SndPath)) {
  418.         $SoundsList = Get-ChildItem -Path $SndPath -File | Where-Object { $_.Extension -eq '.wav' }
  419.         $SoundMap = @{
  420.             "start" = "SystemStart"; "boot" = "SystemStart"; "welcome" = "SystemStart";
  421.             "connect" = "DeviceConnect"; "plug" = "DeviceConnect"; "insert" = "DeviceConnect";
  422.             "disconnect" = "DeviceDisconnect"; "unplug" = "DeviceDisconnect";
  423.             "fail" = "DeviceFail"; "devicefail" = "DeviceFail";
  424.             "recycle" = "Explorer\EmptyRecycleBin"; "trash" = "Explorer\EmptyRecycleBin"; "empty" = "Explorer\EmptyRecycleBin";
  425.             "notification" = "SystemNotification"; "notify" = "SystemNotification";
  426.             "critical" = "SystemHand"; "error" = "SystemHand"; "stop" = "SystemHand";
  427.             "warning" = "SystemExclamation"; "warn" = "SystemExclamation"; "exclamation" = "SystemExclamation";
  428.             "default" = ".Default"; "beep" = ".Default";
  429.             "criticalbattery" = "CriticalBatteryAlarm";
  430.             "lowbattery" = "LowBatteryAlarm";
  431.             "mail" = "MailBeep"; "email" = "MailBeep";
  432.             "restoredown" = "RestoreDown"; "restoreup" = "RestoreUp";
  433.             "asterisk" = "SystemAsterisk";
  434.             "logoff" = "WindowsLogoff";
  435.             "logon" = "WindowsLogon";
  436.             "uac" = "WindowsUAC"; "admin" = "WindowsUAC";
  437.             "theme" = "ChangeTheme"
  438.         }
  439.         foreach ($SndFile in $SoundsList) {
  440.             $LowerName = $SndFile.BaseName.ToLower()
  441.             foreach ($Key in $SoundMap.Keys) {
  442.                 if ($LowerName.Contains($Key)) {
  443.                     $EventPath = $SoundMap[$Key]
  444.                    
  445.                     if ($EventPath -match "^Explorer\\") {
  446.                         $Section = "[AppEvents\Schemes\Apps\$EventPath]"
  447.                     } else {
  448.                         $Section = "[AppEvents\Schemes\Apps\.Default\$EventPath]"
  449.                     }
  450.                    
  451.                     @("$Section", "DefaultValue=$($SndFile.Name)", "") | Out-File $ThemeFile -Encoding UTF8 -Append
  452.                     $CabinetFilesList += New-Object PSObject -Property @{ SourcePath = $SndFile.FullName; CabinetSubdir = "" }
  453.                     break
  454.                 }
  455.             }
  456.         }
  457.     }
  458.  
  459.     # 4. Mandatory Coupling Section Closures
  460.     @("[VisualStyles]", "Path=%SystemRoot%\resources\Themes\Aero\Aero.msstyles", "ColorStyle=NormalColor", "Size=NormalSize", "AutoColorization=$AutoColorValue", "ColorizationColor=$HexColorValue", "SystemMode=$SysValue", "AppMode=$AppValue", "VisualStyleVersion=10", "Transparency=1") | Out-File $ThemeFile -Encoding UTF8 -Append
  461.  
  462.     # 5. Generate DDF compilation blueprint instructions with strict absolute paths
  463.     $DdfFile = Join-Path $StagingDir "pack.ddf"
  464.     $DdfContent = @(
  465.         ".OPTION EXPLICIT",
  466.         ".Set CabinetNameTemplate=$CleanThemeName.deskthemepack",
  467.         ".Set Cabinet=on",
  468.         ".Set Compress=on",
  469.         ".Set CompressionType=MSZIP",
  470.         ".Set DiskDirectoryTemplate=.",
  471.         ".Set CabinetFileCountThreshold=0",
  472.         ".Set FolderFileCountThreshold=0",
  473.         ".Set FolderSizeThreshold=0",
  474.         ".Set MaxCabinetSize=0",
  475.         ".Set MaxDiskFileCount=0",
  476.         ".Set MaxDiskSize=0",
  477.         "",
  478.         ".Set DestinationDir=",
  479.         "`"$ThemeFile`""
  480.     )
  481.    
  482.     $RootFiles = $CabinetFilesList | Where-Object { $_.CabinetSubdir -eq "" }
  483.     if ($RootFiles) {
  484.         $DdfContent += ""
  485.         $DdfContent += ".Set DestinationDir="
  486.         foreach ($F in $RootFiles) { $DdfContent += "`"$($F.SourcePath)`"" }
  487.     }
  488.    
  489.     $BgFiles = $CabinetFilesList | Where-Object { $_.CabinetSubdir -eq "DesktopBackground" }
  490.     if ($BgFiles) {
  491.         $DdfContent += ""
  492.         $DdfContent += ".Set DestinationDir=DesktopBackground"
  493.         foreach ($F in $BgFiles) { $DdfContent += "`"$($F.SourcePath)`"" }
  494.     }
  495.  
  496.     $DdfContent | Out-File $DdfFile -Encoding ASCII
  497.  
  498.     # Open save file panel layout dialog
  499.     $SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
  500.     $SaveFileDialog.Filter = "Windows Theme Pack (*.deskthemepack)|*.deskthemepack"
  501.     $SaveFileDialog.FileName = "$CleanThemeName.deskthemepack"
  502.    
  503.     if ($SaveFileDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
  504.         $FinalOutName = Split-Path $SaveFileDialog.FileName -Leaf
  505.        
  506.         # Explicitly patch directive text blocks without breaking nested string arrays
  507.         (Get-Content $DdfFile) | ForEach-Object { $_ -replace "CabinetNameTemplate=.*", "CabinetNameTemplate=$FinalOutName" } | Out-File $DdfFile -Encoding ASCII
  508.        
  509.         Start-Process -FilePath "makecab.exe" -ArgumentList "/F `"$DdfFile`"" -WorkingDirectory $StagingDir -NoNewWindow -Wait
  510.        
  511.         if (Test-Path (Join-Path $StagingDir $FinalOutName)) {
  512.             Copy-Item -Path (Join-Path $StagingDir $FinalOutName) -Destination $SaveFileDialog.FileName -Force
  513.             [System.Windows.Forms.MessageBox]::Show("Theme Pack successfully compiled!", "Success")
  514.         } else {
  515.             [System.Windows.Forms.MessageBox]::Show("Compilation failed natively. Check asset properties.", "Error")
  516.         }
  517.     }
  518.     Remove-Item -Path $StagingDir -Recurse -Force -ErrorAction SilentlyContinue
  519. })
  520.  
  521. $Form.Controls.Add($BtnBuild)
  522. $Form.ShowDialog() | Out-Null
Advertisement
Comments
  • Nimbi
    17 days (edited)
    Comment was deleted
  • Nimbi
    17 days (edited)
    Comment was deleted
  • Nimbi
    17 days
    # text 1.51 KB | 0 0
    1. Powershell Script using WinForms to build Windows 11 deskthemepacks using a graphical GUI. Allows for packaging wallpapers, cursors, and sounds. Simply provide the folders with the required assets, select the color modes, and optionally set the slideshow settings.
    2.  
    3. A screenshot can be found here: https://previews.alekeagle.me/-mEhr1ZCIb.png
    4.  
    5. --------------------------------------------------------------------------------------------------------------------------------------------
    6.  
    7. Cursors and sounds need to be named properly for this script to map them to the correct paths.
    8.  
    9. Here is a list of valid cursor file names:
    10.  
    11. - appstarting.ani
    12. - wait.ani
    13. - arrow.cur
    14. - crosshair.cur
    15. - hand.cur
    16. - help.cur
    17. - ibeam.cur
    18. - no.cur
    19. - nwpen.cur
    20. - person.cur
    21. - pin.cur
    22. - sizeall.cur
    23. - sizenesw.cur
    24. - sizens.cur
    25. - sizenwse.cur
    26. - sizewe.cur
    27. - upparow.cur
    28.  
    29. Here is a list of valid sound file names:
    30.  
    31. - change-theme.wav
    32. - critical-battery-alarm.wav
    33. - critical-stop.wav
    34. - default-beep.wav
    35. - desktop-mail-notification.wav
    36. - device-connect.wav
    37. - device-failed-to-connect.wav
    38. - exclamation.wav
    39. - low-battery-alarm.wav
    40. - restore-down.wav
    41. - restore-up.wav
    42. - uac.wav
    43. - windows-logoff.wav
    44. - windows-logon.wav
    45.  
    46.  
    47. The names listed above are not the only names accepted. But they do give a general example of the naming schemes required to ensure the cursors and sounds are mapped correctly. The script uses the names of the files to determine the correct mapping. So the names need to be correct for each cursor and sound file.
Add Comment
Please, Sign In to add comment