33

I want to allow my users to toggle the current user theme between Aero and Windows Classic(1). Is there a way that I can do this programatically?

I don't want to pop up the "Display properties", and I'm dubious about just changing the registry. (This requires a log out and a log back in for the changes to take effect).

Application skinning (using the Codejock libraries) doesn't work either.

Is there a way of doing this?

The application is hosted/run on a Windows Server 2008 over RDP.

(1) The application in question is a hosted "Remote App", and I want users to be able to change the look of the displayed application to match their desktop.

Cœur
  • 37,241
  • 25
  • 195
  • 267
seanyboy
  • 5,623
  • 7
  • 43
  • 56

13 Answers13

69

You can set it using the following command:

rundll32.exe %SystemRoot%\system32\shell32.dll,Control_RunDLL %SystemRoot%\system32\desk.cpl desk,@Themes /Action:OpenTheme /file:"C:\Windows\Resources\Themes\aero.theme"

Caveat is that this will show the theme selector dialog. You could kill that dialog straight after.

Campbell
  • 2,848
  • 3
  • 28
  • 26
  • 10
    sick.. how'd you figure that out? – Claudiu Apr 08 '11 at 18:53
  • 6
    imma give this a bounty since it's the actual answer – Claudiu Apr 08 '11 at 21:24
  • I'm guessing it was worked out using ProcessMonitor while changing the Theme manually. – Jeremy Thompson Feb 26 '15 at 05:15
  • 5
    Jeez can't even remember how I worked this out. We used it to set backgrounds in a task sequence to customize the background for corporates from standard images. – Campbell Apr 17 '15 at 17:44
  • (+1) superb just save me Windows reinstall after some bug/virus preset of theme to black on black so no menu or text any where was visible (works on both XP and W7 :) just had to select theme I got) – Spektre Apr 22 '15 at 06:25
  • 2
    I am using your solution but it opens me a window for desktop icon setting and doesn't change the theme.... i can not able to find the issue. On windows 10 – Anoop Mishra Jan 13 '16 at 08:56
  • 1
    "You could kill that dialog straight after." Could you please explain how? – Max Feb 19 '17 at 18:29
  • Here's the [way I am doing it](https://stackoverflow.com/a/49024803/356887) in powershell. – xdhmoore Feb 28 '18 at 08:14
  • Has anyone got success to convert the command to powershell? Initially faced error on @Theme for splatted variable. After escaping @Theme with ``, the command opens the control panel window however doesn't set the theme. Any suggestion would be appreciated. – Vicky_Burnwal Sep 25 '18 at 08:25
  • @Vicky_Burnwal it's much simpler in Powershell: `Start "C:\Windows\Resources\Themes\aero.theme"`. – ETL Nov 19 '21 at 21:25
19

There are certainly good reasons for wanting to change the current theme programmatically. E.g. an automated test tool may need to switch between various themes to make sure the application works correctly with all of them.

As a user, you can change the theme by double-clicking a .theme file in Windwos Explorer and then closing the Control Panel applet that pops up. You can easily do the same from code. The steps below work just fine for me. I've only tested on Windows 7.

  1. Use SHGetKnownFolderPath() to get the "Local AppData" folder for the user. Theme files are stored in the Microsoft\Windows\Themes subfolder. Theme files stored there are applied directly, while theme files stored elsewhere are duplicated when you execute them. So it's best to use files from that folder only.
  2. Use ShellExecute() to execute the .theme file you located in step 1.
  3. Wait for the theme to be applied. I simply let my app sleep for 2 seconds.
  4. Call FindWindow('CabinetWClass', 'Personalization') to get the handle of the Control Panel window that popped up when the theme was applied. The "Personalization" caption will likely be different on non-US-English versions of Windows.
  5. Call PostMessage(HWND, WM_CLOSE, 0, 0) to close the Control Panel window.

This isn't a very elegant solution, but it does the job.

Jan Goyvaerts
  • 21,379
  • 7
  • 60
  • 72
  • 2
    I can't find `%LocalAppData%\Microsoft\Windows\Themes` on Windows 8. There is an `%AppData%\Microsoft\Windows\Themes`, but there are no THEME files in there. – XP1 Jul 26 '12 at 01:27
  • Look for `.themepack`. Download or manually customize a theme, right-click and "Save theme for sharing" as a `.themepack` file. This can be "run" and changes the current theme. – Nigel Touch Jun 13 '13 at 17:10
11

I know this is an old ticket, but somebody asked me how to do this today. So starting from Mike's post above I cleaned things up, added comments, and will post full C# console app code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Win32;

namespace Windows7Basic
{
    class Theming
    {
        /// Handles to Win 32 API
        [DllImport("user32.dll", EntryPoint = "FindWindow")]
        private static extern IntPtr FindWindow(string sClassName, string sAppName);
        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        /// Windows Constants
        private const uint WM_CLOSE = 0x10;

        private String StartProcessAndWait(string filename, string arguments, int seconds, ref Boolean bExited)
        {
            String msg = String.Empty;
            Process p = new Process();
            p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
            p.StartInfo.FileName = filename;
            p.StartInfo.Arguments = arguments;
            p.Start();

            bExited = false;
            int counter = 0;
            /// give it "seconds" seconds to run
            while (!bExited && counter < seconds)
            {
                bExited = p.HasExited;
                counter++;
                System.Threading.Thread.Sleep(1000);
            }//while
            if (counter == seconds)
            {
                msg = "Program did not close in expected time.";
            }//if

            return msg;
        }

        public Boolean SwitchTheme(string themePath)
        {
            try
            {    
                //String themePath = System.Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\Resources\Ease of Access Themes\basic.theme";
                /// Set the theme
                Boolean bExited = false;
                /// essentially runs the command line:  rundll32.exe %SystemRoot%\system32\shell32.dll,Control_RunDLL %SystemRoot%\system32\desk.cpl desk,@Themes /Action:OpenTheme /file:"%WINDIR%\Resources\Ease of Access Themes\classic.theme"
                String ThemeOutput = this.StartProcessAndWait("rundll32.exe", System.Environment.GetFolderPath(Environment.SpecialFolder.System) + @"\shell32.dll,Control_RunDLL " + System.Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\desk.cpl desk,@Themes /Action:OpenTheme /file:\"" + themePath + "\"", 30, ref bExited);

                Console.WriteLine(ThemeOutput);

                /// Wait for the theme to be set
                System.Threading.Thread.Sleep(1000);

                /// Close the Theme UI Window
                IntPtr hWndTheming = FindWindow("CabinetWClass", null);
                SendMessage(hWndTheming, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
            }//try
            catch (Exception ex)
            {
                Console.WriteLine("An exception occured while setting the theme: " + ex.Message);

                return false;
            }//catch
            return true;
        }

        public Boolean SwitchToClassicTheme()
        {
            return SwitchTheme(System.Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\Resources\Ease of Access Themes\basic.theme");
        }

        public Boolean SwitchToAeroTheme()
        {
            return SwitchTheme(System.Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\Resources\Themes\aero.theme");
        }

        public string GetTheme()
        {
            string RegistryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes";
            string theme;
            theme = (string)Registry.GetValue(RegistryKey, "CurrentTheme", string.Empty);
            theme = theme.Split('\\').Last().Split('.').First().ToString();
            return theme;
        }

        // end of object Theming
    }

    //---------------------------------------------------------------------------------------------------------------

    class Program
    {
        [DllImport("dwmapi.dll")]
        public static extern IntPtr DwmIsCompositionEnabled(out bool pfEnabled);

        /// ;RunProgram("%USERPROFILE%\AppData\Local\Microsoft\Windows\Themes\themeName.theme")      ;For User Themes
        /// RunProgram("%WINDIR%\Resources\Ease of Access Themes\classic.theme")                     ;For Basic Themes
        /// ;RunProgram("%WINDIR%\Resources\Themes\aero.theme")                                      ;For Aero Themes

        static void Main(string[] args)
        {
            bool aeroEnabled = false;
            Theming thm = new Theming();
            Console.WriteLine("The current theme is " + thm.GetTheme());

            /// The only real difference between Aero and Basic theme is Composition=0 in the [VisualStyles] in Basic (line omitted in Aero)
            /// So test if Composition is enabled
            DwmIsCompositionEnabled(out aeroEnabled);

            if (args.Length == 0 || (args.Length > 0 && args[0].ToLower(CultureInfo.InvariantCulture).Equals("basic")))
            {
                if (aeroEnabled)
                {
                    Console.WriteLine("Setting to basic...");
                    thm.SwitchToClassicTheme();
                }//if
            }//if
            else if (args.Length > 0 || args[0].ToLower(CultureInfo.InvariantCulture).Equals("aero"))
            {
                if (!aeroEnabled)
                {
                    Console.WriteLine("Setting to aero...");
                    thm.SwitchToAeroTheme();
                }//if
            }//else if
        }

        // end of object Program
    }
}

isopropanol
  • 445
  • 4
  • 10
4

I'm not sure if this is a new thing, but you can just double click the .theme file and Windows 10 will apply the theme. Hence, you can do this with PowerShell easily:

$Windows10Theme = "C:\Windows\Resources\Themes\aero.theme"
Invoke-Expression $Windows10Theme
joon
  • 3,899
  • 1
  • 40
  • 53
2

The command for newer Windows versions (Windows 8 and 8.1, haven't tried it on W10 yet) is:

rundll32.exe themecpl.dll,OpenThemeAction %1

or with full paths:

C:\WINDOWS\system32\rundll32.exe C:\WINDOWS\system32\themecpl.dll,OpenThemeAction %LocalAppData%\Microsoft\Windows\Themes\yourtheme.theme

Basically it's the Personalisation CPL "open" command for .theme & .themepack extensions taken from registry...

You'll still end up with the Personalisation window beeing open after using this command so to close it down programatically you'll have to use one of the suggested methods mentioned above... (I personally prefer the Powershell script)

Cube
  • 21
  • 2
2

I have been experimenting about changing the windows theme via command line and I learned that by executing the theme file it is being applied by the Windows 10 as well. So in your batch file, you could use one of the following lines:

C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\Themes\Dark_Mode.theme

or

C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\Themes\Light_Mode.theme

Please note the path to the theme files might be needed to adjust depending on your system user configuration. I strongly advise saving your themes with names excluding spaces as it makes much easier moving forward. Executing such line leaving you with the Settings window opened. To deal with I considered using VBS script instead. Thanks to Patrick Haugh user1390106 there is a much easier way to close the Settings window.

taskkill /F /IM systemsettings.exe

So the updated version of batch file could look like this:

@echo off
if %1 == dark (
REM ================== Go Dark ==================
color 09
echo.
echo   Applying DARK MODE
echo   Windows Theme ...
C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\Themes\Dark_Mode.theme
timeout /T 1 /nobreak > nul
taskkill /F /IM systemsettings.exe > nul
echo   DONE
) else (
REM ============== Return to Light ==============
color 30
echo.
echo   Applying LIGHT MODE
echo   Windows Theme ...
C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\Themes\Light_Mode.theme
timeout /T 1 /nobreak > nul
taskkill /F /IM systemsettings.exe > nul
echo   DONE
)
REM ================== Goodbye ==================
echo.
echo   Goodbye
cls
exit

Please note the path to the theme files might be needed to adjust depending on your system user configuration. Save above script with the name theme.bat somewhere in your drive. This batch file taking one parameter which needs to be either dark or any other string. Then you could prepare two shortcuts to this batch file each with one of the following in the box called “Target” on the “Shortcut” tab in its properties:

C:\full-path-to-your-batch-file\theme.bat dark

or

C:\full-path-to-your-batch-file\theme.bat light

Please replace “full-path-to-your-batch-file” with actual path to that file. Here are links to the videos showing how this works:

a) Going Dark – https://youtu.be/cBcDNhAmfyM

b) Returning to the Light – https://youtu.be/2kYJaJHubi4

Please note that my script in those videos also activating/deactivating the Stylish plug-in for chrome. I have omitted to explain how I accomplished that part as it is not a subject of this article.

Arnold
  • 21
  • 2
1

In addition of the post of "Jan Goyvaerts": I use SendMessage instead of PostMessage. The difference is that SendMessage waits for the command to be taken in by the window. Meaning that in the SendMessages returns, you know that the theme dialog is closed.

So if you start it with the monstrous (but genious) rundll32.exe method suggested by "Campbell". You should wait a sec before sending WM_CLOSE. Otherwise the theme will not be set and the application closes right away.

The code snippet below extracts a file from resource (a themepack). Then executes the desk.cpl with rundll32.exe, waits 3 sceonds, then sends WM_CLOSE (0x0010), waits for the command to be process (the time it takes for the theme to be set).

    private Boolean SwitchToClassicTheme()
    {
        //First unpack the theme
        try
        {
            //Extract the theme from the resource
            String ThemePath = System.Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\Resources\Themes\ClassicTheme.themepack";
            //WriteFileToCurrentDirectory("ClassicTheme.theme", TabletConfigurator.Resources.ClassicTheme);
            if(File.Exists(ThemePath))
            {
                File.Delete(ThemePath);
            }
            if(File.Exists(ThemePath))
            {
                throw new Exception("The file '" + ThemePath + "' exists and can not be deleted. You can try to delete it manually.");
            }
            using (BinaryWriter sw = new BinaryWriter(new FileStream(ThemePath, FileMode.OpenOrCreate)))
            {
                sw.Write(TabletConfigurator.Resources.ClassicTheme);
                sw.Flush();
                sw.Close();
            }

            if(!File.Exists(ThemePath))
            {
                throw new Exception("The resource theme file could not be extracted");
            }

            //Set the theme file as like a user would have clicked it
            Boolean bTimedOut = false;
            String ThemeOutput = StartProcessAndWait("rundll32.exe", System.Environment.GetFolderPath(Environment.SpecialFolder.System) + @"\shell32.dll,Control_RunDLL " + System.Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\desk.cpl desk,@Themes /Action:OpenTheme /file:\"" + ThemePath + "\"", ref bTimedOut);

            System.Threading.Thread.Sleep(3000);
            //Wait for the theme to be set
            IntPtr hWndTheming = FindWindow("CabinetWClass", null);
            SendMessage(hWndTheming, (uint)WM_CLOSE, 0, 0);

            //using (Bitmap bm = CaptureScreenShot())
            //{
            //    Boolean PixelIsGray = true;
            //    while (PixelIsGray)
            //    {
            //        System.Drawing.Color pixel = bm.GetPixel(0, 0)
            //    }
            //}

        }
        catch(Exception ex)
        {
            ShowError("An exception occured while setting the theme: " + ex.Message);
            return false;
        }
        return true;
    }
Mike de Klerk
  • 11,906
  • 8
  • 54
  • 76
  • What is FindWindow()? StartProcessAndWait()? SendMessage()? This code looks useful, but is incomplete as a sample. – Leigh Dec 04 '12 at 09:38
1

I just realized you can double click the theme and it autoswitches it - much simpler, so just executing the theme works, ex batch file:

:: Reactivate my theme after an remote desktop session
:: We must select another theme first before we can select ours again and hence re-activate Aero, please wait..."
@echo Off
"C:\Windows\Resources\Themes\aero.theme"
::echo "Simulating a pause while"
ping 127.0.0.1 -n 10 > null && "D:\Users\danielsokolowski\Windows 7 Aero Themes\`danielsokolowski` Theme (without Glass).theme"
::or ping 127.0.0.1 -n 3 > null && "%userprofile%\AppData\Local\Microsoft\Windows\Themes\`danielsokolowski` Theme (without Glass).theme"
Daniel Sokolowski
  • 11,982
  • 4
  • 69
  • 55
1

I believe the best you can do is open your target .msstyles file (in c:\windows\resources\themes), which will pop up the display properties box. At this point you could use window subclassing to programmatically click the right buttons.

Factor Mystic
  • 26,279
  • 16
  • 79
  • 95
1

For Windows 10 I wrote this simple solution (it can also be used in DSC) in PowerShell

# Apply your theme
& "C:\Windows\Resources\Themes\Brand.theme"
# We need to wait for the theme to be applied
Start-Sleep -s 5
# Close the settings window that is opened by the action above
$window = Get-Process | Where-Object {$_.Name -eq "SystemSettings"}
Stop-Process -Id $window.Id
0

Okay so here is my take on this - a VB script. It's a bit nasty but the best I could come up with (sadly).

For a user that logs in, we simply run ChangeTheme.vbs as the user logs in (e.g. autorun). The script starts desk.cpl and passes the required parameters to it as well as the name of the selected theme.

One can run the script with or without parameters:

> ChangeTheme.vbs
> ChangeTheme.vbs AnyThemeName

The script:

' ////////////////////////////////////////////////////////////////////
'
' Changes the theme.
'
' Name:
'     ChangeTheme.vbs
' Parameter 1: 
'     Theme name e.g. aero or anything 
'     located in in C:\Windows\Resources\Themes. 
'     If not present, a default theme will be used.
'
' Example: 
'     Inside a command line run
'     > ChangeTheme.vbs TheThemeName
'
' ////////////////////////////////////////////////////////////////////

If(Wscript.Arguments.Count <= 0) Then
  ' If no parameter was given we set the following theme as default
  selectedTheme = "aero"
Else
  ' Get theme via the first argument  
  selectedTheme = Wscript.Arguments(0)
End If

' Create WScript shell object
Set WshShell = WScript.CreateObject("WScript.Shell")

' Run the command to open the "theme application" (or whatever)
Set process = WshShell.Exec("rundll32.exe %SystemRoot%\system32\shell32.dll,Control_RunDLL %SystemRoot%\system32\desk.cpl desk,@Themes /Action:OpenTheme /file:""C:\Windows\Resources\Themes\" & selectedTheme & ".theme""")

' Wait for the application to start

Wscript.Sleep 250

Success = False
maxTries = 20
tryCount = 0

Do Until Success = True

  Wscript.Sleep 1000

  ' Set focus to our application    
  ' If this fails, or the application loses focus, it won't work!    
  Success = WshShell.AppActivate(process.ProcessId)

  tryCount = tryCount + 1

  If (tryCount >= maxTries) Then
    ' If it does not work after maxTries we give up ..
    MsgBox("Cannot change theme - max tries exceeded ..")
    Exit Do
  End If

Loop

' The crucial part: Send keys ALT + B for applying the theme    
WshShell.Sendkeys "%(B)"

' Send key "escape" to close the window
WshShell.Sendkeys "{ESCAPE}" 

Hope that helps.

Stefan Falk
  • 23,898
  • 50
  • 191
  • 378
0

It works on Windows 10.

this is my script. It changes the theme and closes the window. I save it to a batch file and run this patch file from TaskScheduler:

C:\WINDOWS\system32\rundll32.exe C:\WINDOWS\system32\themecpl.dll,OpenThemeAction C:\Users\xxx\Misc_computer_stuff\themes\my_fav_gr.theme

TIMEOUT 1 & REM Waits 1 seconds before executing the next command

TASKKILL /F /IM systemsettings.exe & close window

exit
Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
user1390106
  • 23
  • 1
  • 8
0

You can simply open any of the .theme files present in C:\Windows\Resources\Themes\ to change the theme.

The only catch is that the settings app is also opened after this. But we can kill it using Stop-Process in PowerShell

Invoke-Expression "C:\Windows\Resources\Themes\<theme_name>.theme"
Start-Sleep -Seconds 2
Stop-Process -Name SystemSettings

For Example:

Invoke-Expression "C:\Windows\Resources\Themes\dark.theme"