Open a file in Visual Studio at a specific line number - visual-studio

I have a utility (grep) that gives me a list of filenames and a line numbers. After I have determined that devenv is the correct program to open a file, I would like to ensure that it is opened at the indicated line number. In emacs, this would be:
emacs +140 filename.c
I have found nothing like this for Visual Studio (devenv). The closest I have found is:
devenv /Command "Edit.Goto 140" filename.c
However, this makes a separate instance of devenv for each such file. I would rather have something that uses an existing instance.
These variations re-use an existing devenv, but don't go to the indicated line:
devenv /Command "Edit.Goto 140" /Edit filename.c
devenv /Command /Edit filename.c "Edit.Goto 140"
I thought that using multiple "/Command" arguments might do it, but I probably don't have the right one because I either get errors or no response at all (other than opening an empty devenv).
I could write a special macro for devenv, but I would like this utility to be used by others that don't have that macro. And I'm not clear on how to invoke that macro with the "/Command" option.
Any ideas?
Well, it doesn't appear that there is a way to do this as I wanted. Since it looks like I'll need to have dedicated code to start up Visual Studio, I've decided to use EnvDTE as shown below. Hopefully this will help somebody else.
#include "stdafx.h"
//-----------------------------------------------------------------------
// This code is blatently stolen from http://benbuck.com/archives/13
//
// This is from the blog of somebody called "BenBuck" for which there
// seems to be no information.
//-----------------------------------------------------------------------
// import EnvDTE
#pragma warning(disable : 4278)
#pragma warning(disable : 4146)
#import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("8.0") lcid("0") raw_interfaces_only named_guids
#pragma warning(default : 4146)
#pragma warning(default : 4278)
bool visual_studio_open_file(char const *filename, unsigned int line)
{
HRESULT result;
CLSID clsid;
result = ::CLSIDFromProgID(L"VisualStudio.DTE", &clsid);
if (FAILED(result))
return false;
CComPtr<IUnknown> punk;
result = ::GetActiveObject(clsid, NULL, &punk);
if (FAILED(result))
return false;
CComPtr<EnvDTE::_DTE> DTE;
DTE = punk;
CComPtr<EnvDTE::ItemOperations> item_ops;
result = DTE->get_ItemOperations(&item_ops);
if (FAILED(result))
return false;
CComBSTR bstrFileName(filename);
CComBSTR bstrKind(EnvDTE::vsViewKindTextView);
CComPtr<EnvDTE::Window> window;
result = item_ops->OpenFile(bstrFileName, bstrKind, &window);
if (FAILED(result))
return false;
CComPtr<EnvDTE::Document> doc;
result = DTE->get_ActiveDocument(&doc);
if (FAILED(result))
return false;
CComPtr<IDispatch> selection_dispatch;
result = doc->get_Selection(&selection_dispatch);
if (FAILED(result))
return false;
CComPtr<EnvDTE::TextSelection> selection;
result = selection_dispatch->QueryInterface(&selection);
if (FAILED(result))
return false;
result = selection->GotoLine(line, TRUE);
if (FAILED(result))
return false;
return true;
}

With VS2008 SP1, you can use the following command line to open a file at a specific line in an existing instance :
devenv /edit FILE_PATH /command "edit.goto FILE_LINE"
Source

Elaborating on Harold question and answer, I adapted the C++ solution (that I first adopted) to C#. It is much simpler (that is my first C# program!). One just need to create a project, add references to "envDTE" and "envDTE80" and drop the following code:
using System;
using System.Collections.Generic;
using System.Text;
namespace openStudioFileLine
{
class Program
{
[STAThread]
static void Main(string[] args)
{
try
{
String filename = args[0];
int fileline;
int.TryParse(args[1], out fileline);
EnvDTE80.DTE2 dte2;
dte2 = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");
dte2.MainWindow.Activate();
EnvDTE.Window w = dte2.ItemOperations.OpenFile(filename, EnvDTE.Constants.vsViewKindTextView);
((EnvDTE.TextSelection)dte2.ActiveDocument.Selection).GotoLine(fileline, true);
}
catch (Exception e)
{
Console.Write(e.Message);
}
}
}
}
One then just calls openStudioFileLine path_to_file numberOfLine.
Hope that may help !

Based on reder answer I have published repository with source, here is binary(.net2.0)
I also add support for multiple VS versions
usage: <version> <file path> <line number>
Visual Studio version value
VisualStudio 2002 2
VisualStudio 2003 3
VisualStudio 2005 5
VisualStudio 2008 8
VisualStudio 2010 10
VisualStudio 2012 12
VisualStudio 2013 13
Example using from GrepWin:
VisualStudioFileOpenTool.exe 12 %path% %line%

Pretty old thread, but it got me started so here's another example. This AutoHotkey function opens a file, and puts the cursor on a particular rowand column.
; http://msdn.microsoft.com/en-us/library/envdte.textselection.aspx
; http://msdn.microsoft.com/en-us/library/envdte.textselection.movetodisplaycolumn.aspx
VST_Goto(Filename, Row:=1, Col:=1) {
DTE := ComObjActive("VisualStudio.DTE.12.0")
DTE.ExecuteCommand("File.OpenFile", Filename)
DTE.ActiveDocument.Selection.MoveToDisplayColumn(Row, Col)
}
Call with:
VST_Goto("C:\Palabra\.NET\Addin\EscDoc\EscDoc.cs", 328, 40)
You could translate it pretty much line by line to VBScript or JScript.

Here is Python variation of Harold's solution:
import sys
import win32com.client
filename = sys.argv[1]
line = int(sys.argv[2])
column = int(sys.argv[3])
dte = win32com.client.GetActiveObject("VisualStudio.DTE")
dte.MainWindow.Activate
dte.ItemOperations.OpenFile(filename)
dte.ActiveDocument.Selection.MoveToLineAndOffset(line, column+1)
It shows how to go to specified line + column.

Here is VBS variation of Harold's solution: link to .vbs script.
open-in-msvs.vbs full-path-to-file line column
Windows supports VBScript natively - no need for compilation or any additional interpreters.

These C# dependencies on project references are completely unecessary. Indeed much of the code here is overly verbose. All you need is this.
using System.Reflection;
using System.Runtime.InteropServices;
private static void OpenFileAtLine(string file, int line) {
object vs = Marshal.GetActiveObject("VisualStudio.DTE");
object ops = vs.GetType().InvokeMember("ItemOperations", BindingFlags.GetProperty, null, vs, null);
object window = ops.GetType().InvokeMember("OpenFile", BindingFlags.InvokeMethod, null, ops, new object[] { file });
object selection = window.GetType().InvokeMember("Selection", BindingFlags.GetProperty, null, window, null);
selection.GetType().InvokeMember("GotoLine", BindingFlags.InvokeMethod, null, selection, new object[] { line, true });
}
Simples eh?

This is my working C# solution for Visual Studio 2017 (15.9.7)
For other versions of VS just change the version number (i.e. "VisualStudio.DTE.14.0")
todo:
Add Reference->Search 'envdte'->Check Checkbox for envdte->Click OK
using EnvDTE;
private static void OpenFileAtLine(string file, int line)
{
DTE dte = (DTE) Marshal.GetActiveObject("VisualStudio.DTE.15.0");
dte.MainWindow.Visible = true;
dte.ExecuteCommand("File.OpenFile", file);
dte.ExecuteCommand("Edit.GoTo", line.ToString());
}

For reference here is the ENVDE written in C# (using O2 Platform inside VisualStudio to get a reference to the live DTE object)
var visualStudio = new API_VisualStudio_2010();
var vsDTE = visualStudio.VsAddIn.VS_Dte;
//var document = (Document)vsDTE.ActiveDocument;
//var window = (Window)document.Windows.first();
var textSelection = (TextSelection)vsDTE.ActiveDocument.Selection;
var selectedLine = 1;
20.loop(100,()=>{
textSelection.GotoLine(selectedLine++);
textSelection.SelectLine();
});
return textSelection;
This code does a little animation where 20 lines are selected (with a 100ms interval)

The correct wingrep command line syntax to force a new instance and jump to a line number is:
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" $F /command "edit.goto $L"
Replace the studio version number with the correct version for your setup.

The version posted by #Mungo64 worked for me, but of course the version number is always changing, so I made a version that automatically searches until we find it.
Add Reference->Search 'envdte'->Check Checkbox for envdte->Click OK
//using EnvDTE; //I didn't use the using directive as it causes ambiguity in another module I'm using.
private static void OpenFileAtLine(string file, int line)
{
//The number needs to be rolled to the next version each time a new version of visual studio is used...
EnvDTE.DTE dte = null;
for (int i = 25; i > 8; i--) {
try
{
dte = (EnvDTE.DTE)Marshal.GetActiveObject("VisualStudio.DTE." + i.ToString() + ".0");
}
catch (Exception ex)
{
//don't care... just keep bashing head against wall until success
}
}
//the following line works fine for visual studio 2019:
//EnvDTE.DTE dte = (EnvDTE.DTE)Marshal.GetActiveObject("VisualStudio.DTE.16.0");
dte.MainWindow.Visible = true;
dte.ExecuteCommand("File.OpenFile", file);
dte.ExecuteCommand("Edit.GoTo", line.ToString());
}

I can't figure out a way to do this with straight command-line options. It looks like you will have to write a macro for it. Supposedly, you can invoke them like so.
devenv /command "Macros.MyMacros.Module1.OpenFavoriteFiles"
So, you can probably create a macro that takes a filename and a line number, then opens the file and jumps to the proper place. But, I don't know that you can specify a same-instance flag somewhere, or not.

I was about to ask this question because when you get the "yellow screen of death" when debugging a web application, you want to quickly go to the file and line that it gives you in the stacktrace e.g:
[ContractException: Precondition failed: session != null]
System.Diagnostics.Contracts.__ContractsRuntime.TriggerFailure(ContractFailureKind kind, String msg, String userMessage, String conditionTxt, Exception inner) in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Controllers\CustomErrorsPageController.cs:0
System.Diagnostics.Contracts.__ContractsRuntime.ReportFailure(ContractFailureKind kind, String msg, String conditionTxt, Exception inner) in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Controllers\CustomErrorsPageController.cs:0
System.Diagnostics.Contracts.__ContractsRuntime.Requires(Boolean condition, String msg, String conditionTxt) in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Controllers\CustomErrorsPageController.cs:0
IAS_UI.Web.IAS_Session..ctor(HttpSessionStateBase session) in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Web\IAS_Session.cs:15
IAS_UI.Controllers.ServiceUserController..ctor() in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Controllers\ServiceUserController.cs:41
Say I want to go to ServiceUserController.cs at line 41. Usually I would open Visual Studio and do it manually but then I wrote a little Autohotkey script which does it.
To open it, you will highlight the filename and line number e.g. ServiceUserController.cs:41 and thereafter press your shortcut Alt + v. Here is the code for it:
$!v::
if (NOT ProcessExists("devenv.exe"))
{
MsgBox, % "Visual Studio is not loaded"
}
else
{
IfWinExist, Microsoft Visual Studio
{
ToolTip, Opening Visual Studio...
c := GetClip()
if (NOT c) {
MsgBox, % "No text selected"
}
else
{
WinActivate ; now activate visual studio
Sleep, 50
; for now assume that there is only one instance of visual studio - handling of multiple instances comes in later
arr := StringSplitF(c, ":")
if (arr.MaxIndex() <> 2) {
MsgBox, % "Text: '" . c . "' is invalid."
}
else {
fileName := arr[1]
lineNumber := arr[2]
; give focus to the "Find" box
SendInput, ^d
; delete the contents of the "Find" box
SendInput, {Home}
SendInput, +{End}
SendInput, {Delete}
; input *** >of FILENAME *** into the "Find" box
SendInput, >of{Space}
SendInput, % fileName
; select the first entry in the drop down list
SendInput, {Down}
SendInput, {Enter}
; lineNumber := 12 remove later
; open the go to line dialog
SendInput, ^g
Sleep, 20
; send the file number and press enter
SendInput, % lineNumber
SendInput {Enter}
}
}
ToolTip
}
}
return
You will want to paste the following "utility functions" before it:
GetClip()
{
ClipSaved := ClipboardAll
Clipboard=
Sleep, 30
Send ^c
ClipWait, 2
Sleep, 30
Gc := Clipboard
Clipboard := ClipSaved
ClipSaved=
return Gc
}
ProcessExists(procName)
{
Process, Exist, %procName%
return (ErrorLevel != 0)
}
StringSplitF(str, delimeters)
{
Arr := Object()
Loop, parse, str, %delimeters%,
{
Arr.Insert(A_LoopField)
}
return Arr
}

Using this command works for me, as long as Visual Studio is NOT open already.
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" /edit "ABSOLUTEFILEPATH_FILENAME.CPP" /command "Edit.GoTo 164"
If it is already open, then sometimes it works and goes to the right line, but then it just stops working and I have never figured out why. Looks like Microsoft is aware of the issue but have said they "Will Not Fix" it, unless more people complain. So if it's still an issue I'd suggest commenting here: https://connect.microsoft.com/VisualStudio/Feedback/Details/1128717

Slightly simplified version of the answer from OnceUponATimeInTheWest:
using System.Runtime.InteropServices;
private static void OpenFileAtLine(string file, int line) {
dynamic vs = Marshal.GetActiveObject("VisualStudio.DTE");
dynamic window = vs.ItemOperations.OpenFile(path);
window.Selection.GotoLine(line, true);
}
It uses dynamics instead of Reflection to make the code a bit shorter and more readable.

Related

How do I pass line number to a method

In Visual Stuido 2013, working in C# (.Net 4.5), how can I pass a line number to a method call. I recall in C there was a #pragma lineNumber to do this, but searching on those terms brings up nothing.
I want to write a method something like this:
// unchecked code:
private void printResetStopwatch(int lineNumber)
{
stopwatch.stop();
System.Console.WriteLine(stopwatch.Elapsed.ToString() + " at line " + lineNumber.ToString();
}
and I would call it something like
printResetStopwatch(#pragma lineNumber);
if #pragma was the answer.
The way to do this is to attribute a parameter on the method with the CallerLineNumberAttribute and provide it with a default value. C# will then fill it in with the line number of the caller
void Method(string message, [CallerLineNumber] int lineNumber = 0) {
...
}
Method("foo"); // C# will insert the line number here
Note that there are actually a set of related attributes here that might interest you. Here is a sample
public void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
Full Documentation: http://msdn.microsoft.com/en-us/library/hh534540.aspx
Note: This requires the C# 5.0 compiler which is included in VS 2013.

Windows Common File Dialog Will Not Display While Running in Visual Studio 2010 Debugger

I have a VS2010 C++ application that uses the Windows Common File Dialog in a pretty ordinary fashion. My company just updated my workstation to a nice quad core CPU with Windows 7 whereas my previous system was still running XP. When I run my application inside the Visual Studio debugger, any attempt to call the CFD seems to fail silently with the code throwing no apparant errors to the output window, and no dialog appearing. Outside the debugger things work just fine. I invoke the dialog pretty much the same way every time.
CString theFilterList = "CSV Import Files (*.csv)|*.csv";
theFilterList = theFilterList + "|All files (*.*)|*.*||";
// construct the common dialog
CFileDialog fileDlg(TRUE, NULL, NULL,OFN_ENABLESIZING | OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_ALLOWMULTISELECT, theFilterList,this);
POSITION pos;
// Initialize m_ofn structure
fileDlg.m_ofn.lpstrTitle = "Please select a UP supplied CSV file to prepare to load into SCT.";
// Create buffer for file names.
const DWORD bufferSize = (MAX_MULTISELECT_FILENAMES * MAX_FILENAMESIZE) + 1;
TCHAR* filenamesBuffer = new TCHAR[bufferSize];
// Initialize beginning and end of buffer.
filenamesBuffer[0] = NULL;
filenamesBuffer[bufferSize-1] = NULL;
// Attach buffer to OPENFILENAME member.
fileDlg.m_ofn.lpstrFile = filenamesBuffer;
fileDlg.m_ofn.nMaxFile = bufferSize;
if ( fileDlg.DoModal() != IDOK)
{
theCSVFilenameList.RemoveAll();
return;
}
pos = fileDlg.GetStartPosition();
while( pos )
{
theCSVFilenameList.Add(fileDlg.GetNextPathName( pos ));
}
I just had this problem. I found that my StackReserve size was too high. I reduced it and the dialog started coming up. Though in my case it was happening both in and out of the debugger.

Calling functions while in debug mode in VC++ (Immediate Window)

I wonder can I call functions during the debug mode in VC++? Assume that I have a function to which I set a break point at, when the execution stops at that point during debugging, can I call other functions and see their results before proceeding to the next line of code?
I believe you can. I think its called Immediate Window. I use VS2010 Ultimate, so I don't know if it exists in your version.
Ctrl + Alt + I
But this only prints output for when the function returns a value. Also, it may not work in some cases.
Let's say you have :
#include <iostream>
int number = 10; //global
void setNumber(int n);
int main()
{
std::cout<<std::endl; //breakpoint 1 here
setNumber(4);
std::cout<<std::endl; //breakpoint 2 here
}
int getNumberSquared()
{
return number * number;
}
void setNumber(int n)
{
number = n;
}
when you encounter breakpoint 1, press the shortcut and type:
getNumberSquared()
The output will be 100
After encountering breakpoint 2, do the same thing and the output will be 16
Visual studio has the option to jump to a specific statement (right click + set next statement or ctrl+shift+F10), but be aware when doing so. A function call requires registries to be valid, which will most likely not be if you jump across classes or out of scope.

Windows 7 - Taskbar - Pin or Unpin Program Links

As in title, is there any Win32 API to do that?
Don't do this.
I'm 99% sure there isn't an official API for it, for exactly the same reason that there wasn't programmatic access to the old Start Menu's pin list.
In short, most users don't want programs putting junk in their favorites, quick launch, taskbar, etc. so Windows doesn't support you doing as such.
I'm trying to implement a VirtuaWin (opensource virtual desktop software) plugin that allows me to pin different buttons to different virtual desktops. Completely valid reason to use this.
Found the way to pin/unpin it already:
Following code snippet is taken from Chromium shortcut.cc file, nearly unchanged, see also the ShellExecute function at the MSDN
bool TaskbarPinShortcutLink(const wchar_t* shortcut) {
int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarpin", shortcut,
NULL, NULL, 0));
return result > 32;
}
bool TaskbarUnpinShortcutLink(const wchar_t* shortcut) {
int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarunpin",
shortcut, NULL, NULL, 0));
return result > 32;
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Seems pretty straightforward if you know the shortcut. For me though this is not sufficient, I also need to iterate over existing buttons and unpin and repin them on different desktops.
In the comments of a Code Project article it says all you have to do is create a symbolic link in the folder "C:\Users\Username\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar".
But it appears to generally be unsociable practice, as the other comments here have noted.
You can pin/unpin apps via Windows Shell verbs:
http://blogs.technet.com/deploymentguys/archive/2009/04/08/pin-items-to-the-start-menu-or-windows-7-taskbar-via-script.aspx
For API, there is a script-friendly COM library for working with the Shell:
http://msdn.microsoft.com/en-us/library/bb776890%28VS.85%29.aspx
Here is an example written in JScript:
// Warning: untested and probably needs correction
var appFolder = "FOLDER CONTAINING THE APP/SHORTCUT";
var appToPin = "FILENAME OF APP/SHORTCUT";
var shell = new ActiveXObject("Shell.Application");
var folder = shell.NameSpace(appFolder);
var folderItem = folder.ParseName(appToPin);
var itemVerbs = folderItem.Verbs;
for(var i = 0; i < itemVerbs.Count; i++)
{
// You have to find the verb by name,
// so if you want to support multiple cultures,
// you have to match against the verb text for each culture.
if(itemVerbs[i].name.Replace(/&/, "") == "Pin to Start Menu")
{
itemVerbs[i].DoIt();
}
}
Just to put some links on the info as microsoft now offer an official documentation on "Taskbar Extensions" :
A small set of applications are pinned
by default for new installations.
Other than these, only the user can
pin further applications; programmatic
pinning by an application is not
permitted.
So Kevin Montrose answer is the correct one : DON'T.
It works, but not for all OS, e.g. Windows 10:
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string dllName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int LoadString(IntPtr hInstance, uint uID, StringBuilder lpBuffer, int nBufferMax);
private static void PinUnpinTaskBar(string filePath, bool pin)
{
if (!File.Exists(filePath))
throw new FileNotFoundException(filePath + " not exists!");
int MAX_PATH = 255;
var actionIndex = pin ? 5386 : 5387; // 5386 is the DLL index for"Pin to Tas&kbar", ref. http://www.win7dll.info/shell32_dll.html
StringBuilder szPinToStartLocalized = new StringBuilder(MAX_PATH);
IntPtr hShell32 = LoadLibrary("Shell32.dll");
LoadString(hShell32, (uint)actionIndex, szPinToStartLocalized, MAX_PATH);
string localizedVerb = szPinToStartLocalized.ToString();
// create the shell application object
dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
string path = Path.GetDirectoryName(filePath);
string fileName = Path.GetFileName(filePath);
dynamic directory = shellApplication.NameSpace(path);
dynamic link = directory.ParseName(fileName);
dynamic verbs = link.Verbs();
for (int i = 0; i < verbs.Count(); i++)
{
dynamic verb = verbs.Item(i);
if ((pin && verb.Name.Equals(localizedVerb)) || (!pin && verb.Name.Contains(localizedVerb)))
{
verb.DoIt();
break;
}
}
}
I found there is no offical API to do that, but someone has do it through VBScript.
http://blog.ananthonline.net/?p=37
Thanks.
this folder contains shortcut of pinned application
C:\Users\Your-User-Name\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar

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