Navigate Shell command not working when the path includes an hash - windows

I'm having problem using the Navigate Shell command when the path include an # sign.
; this will create 2 folders at the root of your C: drive
myPath1 := "C:\delete_me\"
myPath2 := "C:\delete#me\"
if !FileExist(myPath1)
FileCreateDir, %myPath1%
if !FileExist(myPath2)
FileCreateDir, %myPath2%
; make an Explorer active and press Alt-1 and Alt-2
return
!1::
strWinId := WinExist("A")
TrayTip, %myPath1%, %strWinId%
For pExp in ComObjCreate("Shell.Application").Windows
if (pExp.hwnd = strWinId)
try pExp.Navigate(myPath1)
return
!2::
strWinId := WinExist("A")
TrayTip, %myPath2%, %strWinId%
For pExp in ComObjCreate("Shell.Application").Windows
if (pExp.hwnd = strWinId)
try pExp.Navigate(myPath2)
return
Alt-1 works well. But, with Alt-2, the Navigate command returns "file:///C:/delete#me/ » not found.".
If there is no "/" after the "#" (eg myPath := "C:\delete#me"), it works. But this cannot be a solution because the destination path can be deeper in a subfolder (eg. "C:\delete#me\xyz").
I tried to encode the "#", replacing it with "%23", without success. Found nothing on the web or MSDN about that. Any idea?
[keywords: haskmark, hashtag, number sign or pound]

I have what looks to be a working solution for this, which I've also posted here:
4 options to change the current folder in Windows Explorer - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=526&p=153676#p153676
;links:
;Explorer Windows Manipulations - Page 5 - Scripts and Functions - AutoHotkey Community
;https://autohotkey.com/board/topic/19039-explorer-windows-manipulations/page-5#entry297581
;Navigate2 Method (IWebBrowser2)
;https://msdn.microsoft.com/en-us/library/aa752134(v=vs.85).aspx
;4 options to change the current folder in Windows Explorer - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=5&t=526
;windows - Navigate Shell command not working when the path includes an hash - Stack Overflow
;https://stackoverflow.com/questions/22868546/navigate-shell-command-not-working-when-the-path-includes-an-hash
;an AutoHotkey v1.1 script
;note: will create folder: %A_Desktop%\abc#def\abc#def
;q:: ;explorer - navigate to folder (tested on Windows 7)
WinGet, hWnd, ID, A
WinGetClass, vWinClass, % "ahk_id " hWnd
if !(vWinClass = "CabinetWClass") && !(vWinClass = "ExploreWClass")
return
vDir = %A_Desktop%\abc#def\abc#def
;vDir = %A_Desktop%\abc def\abc def
if !FileExist(vDir)
FileCreateDir, % vDir
DllCall("shell32\SHParseDisplayName", WStr,vDir, Ptr,0, PtrP,vPIDL, UInt,0, Ptr,0)
for oWin in ComObjCreate("Shell.Application").Windows
if (oWin.HWND = hWnd)
{
if !InStr(vDir, "#")
oWin.Navigate(vDir)
else
{
VarSetCapacity(SAFEARRAY, A_PtrSize=8?32:24, 0)
NumPut(1, SAFEARRAY, 0, "UShort")
NumPut(1, SAFEARRAY, 4, "UShort")
NumPut(vPIDL, SAFEARRAY, A_PtrSize=8?16:12, "Ptr")
NumPut(DllCall("shell32\ILGetSize", Ptr,vPIDL, UInt), SAFEARRAY, A_PtrSize=8?24:16, "Int")
oWin.Navigate2(ComObject(0x2011,&SAFEARRAY))
DllCall("shell32\ILFree", Ptr,vPIDL)
}
break
}
return

If you want to open a new window, there's no need for COM or unreliable workarounds: just run the folder.
Run C:\delete#me
If you want to open the path in an existing window which is already active, the simplest and most effective workaround is this:
SendInput {F4}{Esc}{Raw}C:\delete#me`n
So in the context of your script, you could use the following function to work around the # when it is present:
Navigate(pExp, myPath2)
;...
Navigate(Exp, Path)
{
if RegExMatch(Path, "#.*\\")
SendInput {F4}{Esc}{Raw}%Path%`n
else
Exp.Navigate(Path)
}

Unfortunately, there does not seem to be a solution to this. Shell.Application Navigate command fails if the path includes a hash (# as in C:\C#Projects).
Using AutoHotkey, the workaround would be to rely on the "second best" approach as identified by the tests in this thread: http://ahkscript.org/boards/viewtopic.php?f=5&t=526.
run, Explorer.exe
Sleep, 500
strFolder := A_ScriptDir
Send, {F4}{Esc}
Sleep, 500
ControlSetText, Edit1, C:\delete#me, A
ControlSend, Edit1, {Enter}, A

When I saw that Navigate couldn't handle hash, I was shocked,
but sure enough I replicated the error.
I thought I'd try the short form path just in case. It works!
if vDir contains #
Loop, %vDir%, 2, 0 ;(0/1/2=files/both/folders, 0/1=recurse no/yes)
vDir := A_LoopFileShortPath

The following approach doesn't require a visible address bar, or SendInput,
also the previous navigation history is maintained.
In the worst-case scenario of a hash in the short-form path of the dir above the target dir,
a go-between folder is used which is navigated to.
A link is created there, invoked, and deleted.
Below, the workaround code is indented, to separate it from the standard code.
A hotkey of ctrl+q, when an Explorer window is active, launches the script.
-
^q:: ;explorer - navigate to directory (use go-between dir if short-form path of dir above target contains #)
WinGet, hWnd, ID, A
WinGetClass, vWinClass, ahk_id %hWnd%
if vWinClass not in CabinetWClass,ExploreWClass
Return
vDir2 = %A_Desktop%\Go-Between ;go-between dir
vDir3 = C:\delete#me ;target dir
if (SubStr(vDir3, 1-1) = "\")
vDir3 := SubStr(vDir3, 1, -1)
if !InStr(FileExist(vDir3), "D")
Return
vPathLnk := ""
if vDir3 contains #
Loop, %vDir3%, 2, 0 ;(0/1/2=files/both/folders, 0/1=recurse no/yes)
vDir3 := A_LoopFileShortPath
;vDir4 is the short-form path of the dir above the target
;paths of problem target dirs are of the form: *#*\*
;where there is at least one hash with a backslash to its right
SplitPath, vDir3, , vDir4
if vDir4 contains #
{
if !InStr(FileExist(vDir2), "D")
FileCreateDir, %vDir2%
if !InStr(FileExist(vDir2), "D")
{
MsgBox error`, go-between dir not found:`r`n%vDir2%
Return
}
vNameLnk = Go-Between.lnk
vPathLnk = %vDir2%\%vNameLnk%
FileCreateShortcut, %vDir3%, %vPathLnk%
}
for oWin in ComObjCreate("Shell.Application").Windows
if (hWnd = oWin.Hwnd)
{
vDir1 := oWin.Document.Folder.Self.Path
if (vDir1 = vDir3)
break
if vDir3 contains #
{
if !(vDir1 = vDir2)
oWin.Navigate(vDir2)
while !(oWin.ReadyState = 4)
Sleep 10
oItem := oWin.Document.Folder.Items.Item(vNameLnk)
oItem.InvokeVerbEx("open")
break
}
oWin.Navigate(vDir3)
break
}
oWin := ""
if !(vPathLnk = "")
FileRecycle, %vPathLnk% ;send to recycle bin
;if !(vPathLnk = "")
;FileDelete, %vPathLnk% ;delete
Return

Related

cmd converts em-dash to hyphen on pasting. Any workaround?

I want to be able to paste file paths into cmd with em dashes (—, alt 0151) in them.
cmd converts them to ones where the em dashes have been replaced by a hyphen.
Manual input:
(Keyboard) D:\—\image.png
(cmd) D:\—\image.png
Entering this would open the file as expected.
Pasted input:
(Clipboard) D:\—\image.png
(cmd) D:\-\image.png
Entering this would give me an error because a directory named hyphen doesn't exist.
This is baffling because the file system supports paths to have such a character - I can access this file if I type the path manually, and programs can open it just fine.
Why convert a character that is supported? If it wasn't supported when the conversion was added, why not remove the conversion when the support was added?
More importantly, how can I work around this while keeping the em dashes? I have programs that depend on such paths and it'd be inconvenient to change them in all of them.
Similar to:
How to deal with an em dash in a filename
Using “En Dash” in an input file to a batch file
Rename Files having EmDash using a Batch File
Changing the code page made no difference.
My workaround was to create an AutoHotKey script to parse the path being pasted and to send alt 0151 whenever it encounters an em dash.
It could be faster, but it works - which is miles better than receiving an error.
#SingleInstance, force
numpad7::
tooltip, exited!
Clipboard := stored
sleep, 300
exitapp
#IfWinActive ahk_exe cmd.exe
$^v::
cliptext := clipboard
stored := ClipboardAll
StringGetPos, garbage, cliptext, —
garbage =
if !ErrorLevel {
Loop, Parse, cliptext
{
char = %A_LoopField%
If (char == "—") {
clipboard := sentence
sendinput, ^v
SendInput {alt down}{numpad0}{numpad1}{numpad5}{numpad1}{alt up}
sentence := ""
} else if (char == "") {
char := " "
gosub define_sentence
} else {
gosub define_sentence
}
}
; send sentence when EOL
gosub define_sentence
Clipboard := sentence := SubStr(sentence, 1, -1)
sendinput, ^v
sleep 200
Clipboard := stored
stored =
sentence =
return
} else {
sendinput, ^v
}
return
define_sentence:
sentence := sentence . char
tool := "s= " . sentence . "`n" . "c= " . char
tooltip, %tool%
return
On another note, the highlight.js for autohotkey doesn't seem to work which is great.

Visual Studio environment variables script

I would like testing and feedback (I'm hoping this is the correct place for this mods please move/delete as appropriate)
As anyone who alters environment variables frequently knows Visual studio doesn't pick them up automatically.
I found exiting and restarting all instances of explorer.exe gets Visual Studio picking up the latest set of environment variables without a restart.
Unfortunately this means you lose all your open windows.
I have written an AutoHotKey (www.AutoHotKey.com) script to get around this.
; Array must be initialised
Full_Path := Object()
; First Array dimension must be setup
Full_Path[j] := A_LoopField
; Generate a list of HWND's for explorer windows
WinGet, id, list, ahk_class CabinetWClass
; iterate over all HWND's filling in our Full_Path array
Loop, %id%
{
; store hwnd in this_id for current loop
this_id := id%A_Index%
; Get the window information for this_id
WinGetText, pathToStore, ahk_id %this_id%
; strips the address out of the text storing in ActiveAddress
StringSplit, ActiveAddress, pathToStore, `n
; Turn's Path Into Variable
pathToStore = %ActiveAddress1%
; Remove's The Beginning "Address:" Phrase
pathToStore := RegExReplace(pathToStore, "^Address: ", "")
; Remove's Carriage Returns Incase it Exist in pathToStore
StringReplace, pathToStore, pathToStore, `r, , all
; Store the result in the Full_Path array
ifExist, %pathToStore%
Full_Path%A_Index% := pathToStore
}
; We can now kill all instances of explorer.exe at command prompt
Loop, %id%
{
; Store hwnd in id array
this_id := id%A_Index%
; get process id to kill from stored hwnd
WinGet, pidVal, PID, ahk_id %this_id%
; kill the explorer process
Run, taskkill /f /pid %pidVal%
}
; kill explorer shell
RunWait, taskkill /f /im explorer.exe
; restart explorer shell
Run, explorer
; open all windows we had open previously
Loop, %id%
{
; store actual path to open in local variable path To Open
pathToOpen := Full_Path%A_Index%
; Run explorer providing the correct path to open
Run, explorer %pathToOpen%
}
Return
Any advice or improvements you can make would be greatly appreciated.
Hopefully it will be of use to someone else.
Another approach:
; Get fullpath of all opened explorer windows:
If WinExist("ahk_class CabinetWClass") ; explorer
{
list := ""
; https://autohotkey.com/boards/viewtopic.php?p=28751#p28751
for window in ComObjCreate("Shell.Application").Windows
{
explorer_path := ""
try explorer_path := window.Document.Folder.Self.Path
list .= explorer_path ? explorer_path "`n" : ""
}
list := trim(list, "`n")
; MsgBox, "%list%"
}
; We can now restart the Explorer.exe Process:
RunWait, %comspec% /c taskkill /f /im explorer.exe ,,hide
Process, WaitClose, explorer.exe
Run, explorer.exe
; open all explorer windows we had open previously:
If (list != "")
{
Process, wait, explorer.exe
Loop, parse, list, `n
Run %A_LoopField%
}

AutoHotkey able to capture firefox tab?

It is a bit beyond of my knowledge, so I copy&pasted the whole script.
But I was rejected with a message, which reads, (Firefox version is 28.0) Anybody please help me.
Error: Call to nonexistent function.
Specifically: Acc_Get( ... ...
SetTitleMatchMode 2
WinGet, windows, List, Mozilla Firefox
Loop %windows% {
hwnd := windows%A_Index%
;// Acc_Get(Cmd, ChildPath="", ChildID=0, WinTitle="", WinText="", ExcludeTitle="", ExcludeText="")
page_tab_list := Acc_Get("object", "application.grouping2.property_page.tool_bar3.page_tab_list", "", "ahk_id" hwnd)
For Each, tab in Acc_Children(page_tab_list)
if tab.accName(0) = "https://www.apple.com/" {
tab.accDoDefaultAction(0) ;// remove line to NOT activate tab
WinActivate ahk_id %hwnd%
break 2
}
}
This script simply walks through all tabs until it finds the correct one, via the page title in the window title. Sleep timer can be adjusted.
SetTitleMatchMode 2
needle := "Stack Overflow"
WinActivate, Firefox
Loop {
WinGetTitle, title
IfWinNotActive, Firefox
break
if (InStr(title,needle))
Break
Else
send ^{PgUp}
sleep 50
}
You didn't take all the dependencies from the source. You are missing the required Acc Library.
; Acc.ahk https://github.com/sancarn/ACC.AHK/blob/master/AccV2.ahk
; ACC Tutorial: https://www.autohotkey.com/boards/viewtopic.php?f=7&t=40590

Autohotkey script to open command prompt

I've collected a script from the AutoHotKey forum which lets me open a command prompt at the location I'm open in windows explorer. If the current window is not a explorer window then the prompt opens at the location where the script is present. I would like to change this behavior and make it open from C:\ if the current window is not a explorer window. I've tried to edit the script but its not working as desired.
#ifwinactive, ahk_class CabinetWClass
ControlGetText, address , edit1, ahk_class CabinetWClass
if (address <> "") {
Run, cmd.exe, %address%
}
else {
Run, cmd.exe, "C:"
}
ExitApp
#ifwinactive
The command to run cmd.exe in the c:\ path is
run, cmd.exe, c:\
A full script that would run the cmd window every time would look like this
SetTitleMatchMode, 2
ifwinactive, ahk_class CabinetWClass
ControlGetText, address , edit1, ahk_class CabinetWClass
else
address =
; Exclude specific windows
ifwinactive, My Computer
address =
ifwinactive, My Documents
address =
if (address <> "")
Run, cmd.exe, %address%
else
Run, cmd.exe, C:\
ExitApp
I realize this is an old question, but I was looking into this myself and have a better solution.
Windows has two in-built ways to start cmd at the path of a current explorer window. Shift+RightClick and then click Open Command Window Here (or press w). You can also press alt+d, type cmd, and press enter. So...
LWin & Return::
if WinActive("ahk_class CabinetWClass")
or WinActive("ahk_class ExploreWClass")
{
Send {Shift Down}{AppsKey}{Shift Up}
Sleep 10
Send w{enter}
}
else
{
run, cmd, C:\
}
return
No magically grabbing the address directly from explorer! :)
Couldn't get other answers to work (it has been a few years since they've been written).
I ended up writing this script:
#o::
Send {Alt down}D{Alt up}cmd{enter}
return
Here's a pretty sophisticated script from the AHK forums:
#NoEnv
#SingleInstance Force
#NoTrayIcon
SendMode Input
SetWorkingDir %A_ScriptDir%
SetTitleMatchMode RegEx
#IfWinActive ahk_class ExploreWClass|CabinetWClass|Progman
#c::
WinGetClass WinClass
If ( WinClass = "Progman" )
{
Run %ComSpec% /K cd /D "C:\"
Return
}
If ( InStr( "WIN_7,WIN_VISTA" , A_OSVersion ) )
{
ControlGetText, Path, ToolbarWindow322
RegExMatch(Path, ":\s*(.*)", Path)
Path := Path1
}
Else
{
; Windows XP doesn't know the Edit1 control exists if
; the Address Bar is hidden, so check if it exists and temporarly
; show the Address bar if needed. Temporarly showing the Address bar
; will register the Edit1 control, which contains the path.
ControlGetPos Edit1Pos , , , , Edit1
If ( !Edit1Pos )
{
PostMessage 0x111 , 41477 , 0 , , A ; Show Address Bar
Sleep 100
PostMessage 0x111 , 41477 , 0 , , A ; Hide Address Bar
}
ControlGetText Path , Edit1
}
If ( InStr( Path , ":" ) )
; If( InStr( Path , ":" ) && FileExist(Path) )
Run %ComSpec% /K cd /D "%Path%"
Else
Run %ComSpec% /K cd /D "C:\"
Return
I tweaked the WIN_7 part a little, so that the code is independent of the unreliable Edit1 control, which doesn't always expose the current explorer location or an incorrect one. If ( InStr( Path , ":" ) ) makes sure that there's no custom path like Computer on Windows 7 or My Computer on Windows XP. I also added an alternative condition that additionally checks for the path to exist, if you want to hedge your bets.
Keep it simple. Unless of course you need complexity.
!f1::
run, C:\Windows\System32\cmd.exe
return
!f1 means Alt+F1. For my personal preference. Change it to whatever you like.
Another solution hacked together from here. Works for me on Windows 10, but I admit it's total copy-pasta. Posting in the hopes of saving someone else's eyes from the horror of AHK scripting.
;; Open terminal in current Explorer window folder
#If WinActive("ahk_class CabinetWClass") ; explorer
F4::
WinGetTitle, ActiveTitle, A
If InStr(ActiveTitle, "\") ; If the full path is displayed in the title bar (Folder Options)
Fullpath := ActiveTitle
else
If InStr(ActiveTitle, ":") ; If the title displayed is something like "DriveName (C:)"
{
Fullpath := SubStr(ActiveTitle, -2)
Fullpath := SubStr(Fullpath, 1, -1)
}
else ; If the full path is NOT displayed in the title bar
; https://autohotkey.com/boards/viewtopic.php?p=28751#p28751
for window in ComObjCreate("Shell.Application").Windows
{
try Fullpath := window.Document.Folder.Self.Path
SplitPath, Fullpath, title
If (title = ActiveTitle)
break
}
Run, cmd.exe, %Fullpath%
return
#If

How do I change the current Windows theme programmatically?

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.
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.
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.
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.
Use ShellExecute() to execute the .theme file you located in step 1.
Wait for the theme to be applied. I simply let my app sleep for 2 seconds.
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.
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.
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
}
}
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
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)
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.
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.
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;
}
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"
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
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.
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
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"

Resources