I have a for loop container within my ssis package which contains a script and a sql task.
I have 3 variables.
source.string = this is folder location
file.string = i have used wildcard = *.csv
exist.int = defaulted to 0
I have the innitexpression value set to #Exists=1
and the evalexpression value set to #Exists=1
in the script I have set it to look at source variable and if file.string variable exists then set exist variable to 1
problem is it just loops it should only loop if no file there. cant see how I've done this wrong it was working before I changed the variable to be a wildcard *.csv
I have tested it using another variable which contains a filename rather than a wildcard and it works correctly the issue is when looking for a wildcard for the filename followed by the extension. why is this? can I not pass through a wildcard variable?
my script task is
public void Main()
{
// TODO: Add your code here
string Filepath = Dts.Variables["User::Source"].Value.ToString()
+ Dts.Variables["User::file"].Value.ToString();
if (
File.Exists(Filepath))
{
Dts.Variables["User::Exists"].Value = 1;
}
/// MessageBox.Show (Filepath);
/// MessageBox.Show(Dts.Variables["Exists"].Value.ToString());
Dts.TaskResult = (int)ScriptResults.Success;
}
#region ScriptResults declaration
/// <summary>
/// This enum provides a convenient shorthand within the scope of this class for setting the
/// result of the script.
///
/// This code was generated automatically.
/// </summary>
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
}
}
Based on comments above i made 2 different solutions. The solution for you right now would be no. 2
This one can search for a specific file based on multiple files in your path. It need some tweaking but can be used if you wanna check if a specific file exists with wildcard
This one evaluates to true if any wildcard file is found.
C# Code 1
Using System.IO:
string Filepath = Dts.Variables["User::Source"].Value.ToString();
string WildCard = Dts.Variables["User::file"].Value.ToString(); // In Text form #"*.txt";
string fullpath = Filepath + WildCard;
//With for loop
string txtFile = null;
// Gets all files with wildcard
string[] allfiles = Directory.GetFiles(Filepath, WildCard);
//Loop through all files and set the filename in txtFile. Do whatever you want here
foreach(string fileName in allfiles)
{
//Check if a file contains something, it could be a prefixed name you only want
if(fileName.Contains("txt"))
{
txtFile = fileName;
if(File.Exists(txtFile))
{
Dts.Variables["User::Exists"].Value = 1;
}
}
}
C# Code 2
Using System.IO;
Using System.Linq;
string Filepath = Dts.Variables["User::Source"].Value.ToString();
string WildCard = Dts.Variables["User::file"].Value.ToString(); //In text form "*.txt";
string fullpath = Filepath + WildCard;
//With bool
bool exists = Directory.EnumerateFiles(Filepath, WildCard).Any();
if(exists == true)
{
Dts.Variables["User::Exists"].Value = 1;
}
MessageBox.Show (Filepath);
MessageBox.Show(Dts.Variables["Exists"].Value.ToString());
Hello there I'm working on a windows application using the Windows API with MS VC++ 2010 and I have the following code for selecting folders:
BOOL BrowseFolder(TCHAR *result)
{
BROWSEINFO brwinfo = { 0 };
brwinfo.lpszTitle = _T("Select Your Source Directory");
brwinfo.hwndOwner = hWnd;
LPITEMIDLIST pitemidl = SHBrowseForFolder (&brwinfo);
if (pitemidl == 0) return FALSE;
// get the full path of the folder
TCHAR path[MAX_PATH];
if (SHGetPathFromIDList (pitemidl, path)) result = path;
IMalloc *pMalloc = 0;
if (SUCCEEDED(SHGetMalloc(&pMalloc)))
{
pMalloc->Free(pitemidl);
pMalloc->Release();
}
::MessageBox(hWnd, result, "input", MB_OK);
::MessageBox(hWnd, inputFolder, "input", MB_OK); // Reference Test
return TRUE;
}
So, it opens up a browse folder dialog, saves the selected folder string in the reference parameter "result" and returns true if everything is ok.
Later I call:
BrowseFolder(inputFolder);
And when i try to print out the content of "inputFolder", it shows blank (inputFolder is a global variable TCHAR* inputFolder)
As you can see in the BrowseFolder definition I send two Message Boxes one for "result" and the other for "inputFolder" (this last one shows blank)
So my question is.. If I call: BrowseFolder(inputFolder); Shouldn't "inputFolder" be modified by reference? Why does it show empty?
Thanks in advance.
if (SHGetPathFromIDList (pitemidl, path)) result = path;
This line is your problem. result isn't a class like std::string, it's simply a pointer to a buffer of one or more TCHAR. Assigning it like that simply changes the pointer to point to path, it doesn't copy path into the buffer that result points to.
If you don't want to change to use a string class then you need to call a function to copy the string into the supplied buffer. For example:
StringCchCopy(result, MAX_PATH, path);
(this assumes the buffer is MAX_PATH characters in size).
I am reading a name of the file/script from an utf-8 encoded XML file. This is then passed to VBScript. VBScript starts a second program to which this passed program/script is provided as an argument. When the argument is in English, the VBScript executes successfully.
But if the name read from XML is non-english( Russian in my case ), VBScript fails to find that file.
I am running VBScript from Java code only using "cscript" as I am running it on Windows.
However, if I copy the command fired by Java program to run VBScript, and paste it on command prompt, it executes normally despite the argument name is in non-english language.
I then hardcoded file/script name in VBScript. Changed encoding of VBScript to UCS2-LE, and directly run it from command prompt. It executed normally. It failed to execute for any other encoding used for VBScript. Also the non-english text is displayed as ? in any other encoding than UCS2-LE.
Then I tried to encode file/script name into UTF16-LE in Java and then passed it to VBScript. Irrespective of which encoding was used in VBScript, it fails. Again if I copy the command printed on standard output from Java program and run it from cmd, it executes.
The command printed from Java displays non-english text correctly.
Can anyone please help me to resolve the issue?
Any relative help would be greatly appreciated.
This is what I am doing currently. I need to pass an argument contatining Russian Text to VBScript from Java.
I tried to use two different approaches.
First approach in the code below writes the Russian text in a file using encoding UnicodeLittle. File is found to be in encoding UCS-2LE. And then VBScript reads the value from that file, and script is executed successfully.
In second approach, I tried to directly pass encoded Russian text as argument to script. VbScript fails saying that script can't be opened.This is the approach I want solution for.
Below is the Java code attached.
Any help would be greatly appreciated.
public class CallProgram
{
private static String encodeType = "UnicodeLittle";
private File scriptName = new File( "F:\\Trial Files\\scriptName.txt" );
public static void main(String[] args)
{
CallProgram obj = new CallProgram();
Runtime rt = Runtime.getRuntime();
try
{
**//Approach1 - Writes text to file and calls vbscript which reads text from file and uses it as an argument to a program**
String sName = "D:\\CheckPoints_SCRIPTS\\Менеджер по качеству"; //Russian Text
byte [] encodedByte= sName.getBytes( encodeType );
String testCase = new String( encodedByte, encodeType ); //New string containing russian text in UnicodeLittle encoding...
obj.writeToFile( testCase ); //Writing russian string to file...
String mainStr = "cscript /nologo \"D:\\Program Files\\2.0.1.3\\Adapter\\bin\\scriptRunner_FileRead_Write.vbs\"";
Process proc1 = rt.exec( mainStr );
int exit = proc1.waitFor();
System.out.println( "Exit Value = " + exit );
**//Approach 2 - Passing encoded Russian text directly to VbScript...**
//This is not working for me...
String [] arrArgs = { "cscript", "/nologo", "\"D:\\Program Files\\IBM\\Rational Adapters\\2.0.1.3\\QTPAdapter\\bin\\scriptRunner.vbs\"", testcase };
ProcessBuilder process = new ProcessBuilder( arrArgs );
Process proc2 = process.start();
proc2.waitFor();
}
catch (IOException e)
{
e.printStackTrace();
}
catch ( InterruptedException intue )
{
intue.printStackTrace();
}
}
//Function to write Russian text to file using encoding UnicodeLittle...
private void writeToFile( String testCase )
{
FileOutputStream fos = null;
Writer out = null;
try
{
fos = new FileOutputStream( this.scriptName );
out = new OutputStreamWriter( fos, encodeType );
out.write( testCase );
out.close();
fos.close();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch ( IOException ioe )
{
ioe.printStackTrace();
}
finally
{
try
{
if ( fos != null )
{
fos.close();
fos = null;
}
if ( out != null)
{
out.close();
out = null;
}
}
catch( IOException ioe )
{
fos = null;
out = null;
}
}
} // End of method writeToFile....
}
I've resolved similar problems before by using the short 8.3-style filename instead of the long filename. I get this short name using the ShortPath method of FileSystemObject. Here's a VBScript example... you may want to try something similar in Java.
Function GetShortPath(strLongPath)
Dim FSO
Dim objFolder
Dim objFile
Dim strShortPath
Set FSO = CreateObject("Scripting.FileSystemObject")
' Is it a file or a folder?
If FSO.FolderExists(strLongPath) Then
' It's a folder.
Set objFolder = FSO.GetFolder(strLongPath)
strShortPath = objFolder.ShortPath
ElseIf FSO.FileExists(strLongPath) Then
' It's a file.
Set objFile = FSO.GetFile(strLongPath)
strShortPath = objFile.ShortPath
Else
' File not found.
strShortPath = ""
End If
GetShortPath = strShortPath
End Function
For example,
Debug.Print GetShortPath("C:\öêåéèüø.çõâ")
returns C:\B373~1, which can be used in place of the long filename with non-English characters. Example with dir /x (reveals the short filename) and notepad:
C:\sandbox>dir /x
Volume in drive C has no label.
Volume Serial Number is BC90-DF37
Directory of C:\sandbox
13/01/2011 15:12 <DIR> .
13/01/2011 15:12 <DIR> ..
13/01/2011 14:52 22 NEWTEX~1.TXT New Text Document.txt
13/01/2011 15:05 0 C7F0~1.TXT öêåéèüø.txt
13/01/2011 15:05 0 B373~1 öêåéèüø.çõâ
3 File(s) 22 bytes
2 Dir(s) 342,158,913,536 bytes free
C:\sandbox>notepad B373~1
I've got a Windows XP batch script which cleans some directories, but I would like to move the deleted files to trash instead of using plain del. How is this done?
It looks like the only languages I can use for this is plain batch or Perl.
use Win32::FileOp qw(Recycle);
Recycle(#ARGV);
Write a VBS script (Original Link) then call it with MyDelScript.vbs
function main()
{
if (WScript.Arguments.length != 1)
{
WScript.Echo("<Insert informative error message here>");
return;
}
var Path = WScript.Arguments(0);
var Shell = WScript.CreateObject("Shell.Application");
var Item = Shell.Namespace(0).ParseName(Path);
Item.InvokeVerb("delete");
}
The Win32::FileOp module has a Recycle function. From the docs:
Recycle #filenames
Send the files into the recycle bin. You will not get any confirmation dialogs.
Returns true if successful.
It can be done like this with plain batch and embedded VBScript. Put the following code into a file called recycle.cmd:
<!-- : Begin batch script
#echo off
if "%1"=="" (
echo Usage: %~nx0 FILE_TO_RECYCLE[...]
echo This script puts files into the recycle bin
exit /b 1
)
cscript //nologo "%~f0?.wsf" %*
exit /b %errorlevel%
----- Begin embedded wsf script --->
<job><script language="VBScript">
Set app = WScript.CreateObject("Shell.Application")
Set fso = CreateObject("Scripting.FileSystemObject")
For Each arg In WScript.Arguments
If fso.FileExists(arg) Then
Set file = fso.GetFile(arg)
Set folderItem = app.Namespace(0).ParseName(file.Path)
folderItem.InvokeVerb("delete")
Else
WScript.Echo "File not found: " & arg
End If
Next
</script></job>
Example:
echo This file is dirt.> dirt.txt
echo This file is trash.> trash.txt
recycle dirt.txt trash.txt
As you can see the script allows recycling multiple files with one command.
It does not suppport the wildcards * and ? though.
The idea of embedding VBScript inside a batch file is taken from dbenham's answer to Is it possible to embed and execute VBScript within a batch file without using a temporary file? (scroll down to UPDATE 2014-04-27).
You could use the "recycle" utility which is part of CmdUtils from MaDdoG Software. From the page listing -
Recycle, a safe replacement for the DEL command, that sends files to the recycle bin instead of deleting them. Recycle is also more flexible than DEL; you can specify multiple files at once (or use wildcards), and you can recycle whole directories at once (be careful!)
I would suggest you try its various switches before you incorporate it into your script - there is quite a bit of deviation from the default behaviour of the "del" command.
UPDATE: Contrary to my original claim that the following code does not work, it indeed seems to work. I just forgot that the file I wanted to delete was not in $ENV{TEMP} but a subdirectory of $ENV{TEMP}. The problem is, the file does not go to the Recycle Bin.
The right solution is to use Win32::FileOp but I am going to leave this script here as an example of how to use Win32::API and Win32::API::Struct. I would appreciate it if anyone can point out what I am doing wrong. For your reference:
SHFileOperation: http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx
LPSHFILEOPSTRUCT: http://msdn.microsoft.com/en-us/library/bb759795(VS.85).aspx
#!/usr/bin/perl
use strict;
use warnings;
use File::Spec::Functions qw( catfile );
use Win32::API;
Win32::API::Struct->typedef(
SHFILEOPSTRUCT => qw(
HWND hwnd;
UINT wFunc;
LPCTSTR pFrom;
LPCTSTR pTo;
FILEOP_FLAGS fFlags;
BOOL fAnyOperationsAborted;
LPVOID hNameMappings;
LPCTSTR lpszProgressTitle;
)
);
Win32::API->Import(
shell32 => q{ int SHFileOperation( LPSHFILEOPSTRUCT lpFileOp ) }
);
my $op = Win32::API::Struct->new( 'SHFILEOPSTRUCT' );
$op->{wFunc} = 0x0003; # FO_DELETE from ShellAPI.h
$op->{fFlags} = 0x0040; # FOF_ALLOWUNDO from ShellAPI.h
my $to_delete = catfile( $ENV{TEMP}, "test.file" );
$op->{pFrom} = $to_delete . "\0\0";
my $result = SHFileOperation( $op );
if ( $result ) {
warn sprintf "The operation failed: %4.4X\n", $result;
}
else {
if ( $op->{fAnyOperationsAborted} ) {
warn "Operation was aborted\n";
}
else {
warn "The operation succeeded\n";
}
}
__END__
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"