I want to be able to send the text on the clipboard, in Windows, to an application. For example, I'm working on a text file in notepad, and I want to copy a portion out into a new file..I want to copy it to the clipboard and then use a hotkey to launch an application or powershell script that sends that copied text to a new instance of Notepad.
How can I achieve this in C# or Powershell ?
SOLUTION: Using AutoHotKey
^+c::
Send ^c
Run Notepad
WinWait Untitled - Notepad
WinActivate
Send ^v
return
I have 2 solutions, one that uses PowerShell, the other that uses Autohotkey.
Autohotkey version
I would use this one ;) You define custom key and actions bound to the keys. My file contains this code:
^#n::
Run, Notepad
WinWaitActive Untitled - Notepad2
Send !e
Send p
return
It runs notepad2 and then simulates pressing Alt+E and P. That pastes the string the same way as you would press it by yourself. From some reason I had some problems with 'pressing' Ctrl+V (I don't remember that any more). For more info have a look at Autohotkey's website.
PowerShell version
You need to use an editor like Notepad2. With switch /c it launches the Notepad2 and pastes the text from clipboard.
To make it more useful I use function tnp defined like this:
(note that you need to run PowerShell with -sta parameter, otherwise they won't to work propely)
function tnp {
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[object]
$InputObject
)
begin { $objs = #() }
process { $objs += $InputObject }
end {
$old = Get-clipboard # store current value
$objs | out-string -width 1000 | Set-Clipboard
notepad /c
sleep -mil 500
$old | Set-Clipboard # restore the original value
}
}
function Set-Clipboard {
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)][object]$s
)
begin { $sb = new-object Text.StringBuilder }
process {
$s | % {
if ($sb.Length -gt 0) { $null = $sb.AppendLine(); }
$null = $sb.Append($_)
}
}
end { Add-Type –a system.windows.forms; [windows.forms.clipboard]::SetText($sb.Tostring()) }
}
function Get-Clipboard {
Add-Type –a system.windows.forms
[windows.forms.clipboard]::GetText()
}
With these function you can run something like this:
# gets list of members, opens Notepad2 and pastes the content (members list)
(get-date) | gm | tnp
In other words -- if some info would be returned and formatted to screen, you can get it and paste to notepad.
To get you started, in the excellent PowerShell Community Extensions library there is Get-Clipboard cmdlet that gets the content's of the current clipboard. From there it's fairly trivial to do whatever you want with the clipboard data, such as:
Get-Clipboard > test.txt; notepad test.txt
Running the above gets the current clipboard contents, sets them into test.txt and then opens test.txt in notepad.
One (hackish) strategy would be:
Start the process.
Activate its main window.
Simulate key-strokes as required.
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[STAThread]
static void Main()
{
var p = Process.Start("Notepad.exe");
p.WaitForInputIdle();
SetForegroundWindow(p.MainWindowHandle); // this can probably be left out.
SendKeys.SendWait(Clipboard.GetText());
}
In the specific case of a text-editor like notepad that accepts a path to a text-file as a command-line argument, you could do something more robust but less flexible:
[STAThread]
static void Main()
{
var tempFilePath = Path.GetTempFileName();
File.WriteAllText(tempFilePath , Clipboard.GetText());
Process.Start("Notepad.exe", tempFilePath);
}
If you end up using AutoHotKey, Add ClipWait to make sure AutoHotKey waits for Windows to actually change the clipboard
^+c::
Send ^c
ClipWait
Run Notepad
WinWait Untitled - Notepad
WinActivate
Send ^v
return
If you only want to use the clipboard as a temporary means to transfer the text (thus not lose what you previously saved in the clipboard), you can add something like the following:
^+c::
ClipSaved := ClipboardAll ; Save the entire clipboard to a variable of your choice.
Send ^c
ClipWait ; Wait for the clipboard to change
Run Notepad
WinWait Untitled - Notepad
WinActivate
Send ^v
Clipboard := ClipSaved ; Restore the original clipboard.
ClipSaved = ; Free the memory in case the clipboard was very large.
return
Dim temp = System.IO.Path.GetTempFileName()
System.IO.File.WriteAllText(temp, Clipboard.GetText())
Process.Start("Notepad.exe", temp)
Related
I'm trying to enumerates all open File Explorer windows in a PowerShell script.
I have already found on other posts how to enumerate all explorer.exe windows instances, for example using the Shell.Application COM API:
(New-Object -com "Shell.Application").windows()
But this actually returns more than I want:
I want only the "real" File Explorer windows showing actual files on my disk or network, not the "fake" explorer.exe instances that are just containers for various Control Panel windows, etc.
So basically the list of instances shown when hovering the mouse over the File Explorer icon on the Taskbar.
How can this be done reliably, and preferably in a way that works in Windows 7 to 11?
Comparing the window title to known strings like "Control Panel" or "Windows Update" has limited value. This would only eliminate the most common cases, and on English versions of Windows only.
I tried looking at the File Explorer window class, but it's "CabinetWClass" in all cases, even for Control Panels.
I noticed that real instances have a child window of class "UIRibbonWorkPane", whereas the Control Panel does not. But the ribbon can be disabled, so this is not a reliable marker.
My script already contains C# declarations encapsulating WIN32 API calls, so C# code snippets would also do.
2021-10-10 update:
The best algorithm I've found so far, building on #simon-mourier's answer, can summarized this way:
$self = $window.Document.Folder.Self
$ClassID = $Self.ExtendedProperty("System.NamespaceCLSID")
$BaseClassID = $Self.Path.Substring(2,38) # With proper tests to clear it if it's not a UUID
$FileExplorerIDs = ( # The few known types which are file systems, but don't set $Self.IsFileSystem
# Windows 10
"f02c1a0d-be21-4350-88b0-7367fc96ef3c", # Network
"679f85cb-0220-4080-b29b-5540cc05aab6", # Quick Access
"20d04fe0-3aea-1069-a2d8-08002b30309d", # This PC
# Windows 7
"031e4825-7b94-4dc3-b131-e946b44c8dd5" # Libraries
)
if ($Self.IsFileSystem) {
$AppType = "File Explorer"
} elseif ($FileExplorerIDs -contains "$ClassID") {
$AppType = "File Explorer"
} elseif ($BaseClassID -eq "{26EE0668-A00A-44D7-9371-BEB064C98683}") {
$AppType = "Control Panel"
} elseif ("{$ClassID}" -eq "{D20EA4E1-3957-11D2-A40B-0C5020524153}") {
$AppType = "Control Panel" # Windows 7 Administrative Tools
} elseif ($Self.Name -eq $Self.Path) { # TODO: Improve this test, which is very weak
$AppType = "Search Results" # Ex: "Search Results in Indexed Locations"
} else {
$AppType = "Unknown"
}
The full algorithm, with the proper precautions to eliminate undefined fields, or invalid values, etc, is implemented in this script:
https://github.com/JFLarvoire/SysToolsLib/blob/master/PowerShell/ShellApp.ps1
One solution is to test whether the Shell Folder (IShellFolder) beneath the Shell View that Windows sends back is handled by the Windows file system or by some custom folder.
For that, you can use the System.NamespaceCLSID Windows property. If the folder associated with the view is handled by the file system, this property value will be the ShellFSFolder GUID value which equal to f3364ba0-65b9-11ce-a9ba-00aa004ae837 (from Windows SDK shobjidl_core.h).
You can test it with something like this in PowerShell:
$ShellFSFolder = [System.Guid]::New("f3364ba0-65b9-11ce-a9ba-00aa004ae837")
foreach($win in (New-Object -com "Shell.Application").Windows()) {
$clsid = $win.Document.Folder.Self.ExtendedProperty("System.NamespaceCLSID")
if ($clsid -ne $null) {
$clsid = [System.Guid]::New($clsid)
if ($clsid -eq $ShellFSFolder) {
Write-Host $win.Document.Folder.Self.Path
}
}
}
And like this in C#:
var ShellFSFolder = new Guid("f3364ba0-65b9-11ce-a9ba-00aa004ae837");
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
foreach (var win in shell.Windows)
{
var clsid = win.Document.Folder.Self.ExtendedProperty("System.NamespaceCLSID");
if (clsid != null)
{
Guid guid;
if (clsid is byte[] bytes)
{
guid = new Guid(bytes);
}
else
{
guid = new Guid((string)clsid);
}
if (guid == ShellFSFolder)
{
Console.WriteLine(win.Document.Folder.Title); // for example
}
}
}
It seems that only file-path-based File Explorer windows have a non-$null .LocationUrl property value, so you can filter by that:
Caveat: Jean-François reports that this approach doesn't work for Explorer windows that are open to a file-system folder located on a connected smartphone, in which case .LocationUrl is apparently $null too.
$explorerWinsWithFilePaths =
(New-Object -com "Shell.Application").Windows() | Where-Object LocationUrl
To extract the file paths that these windows are displaying (the technique also works with non-file locations such as Quick Access, which translate into ::-prefixed GUIDs):
$explorerWinsWithFilePaths.Document.Folder.Self.Path
See Jean-François' comment below for examples of what windows showing folders on a connected smartphone report.
I have a C# class library that provides a number of interfaces that can be called from PowerShell scripts (.PS1) and Advanced Modules (.PSM1). I have a static method to write verbose and debug messages to the console using the System.Console class:
public class Write
{
public static void Verbose(string msg, string source)
{
if (Config.EnableVerbose)
{
ConsoleColor originalForeGroundColor = Console.ForegroundColor;
ConsoleColor originalBackGroundColor = Console.BackgroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.BackgroundColor = ConsoleColor.Black;
Console.Write("VERBOSE: {0} {1}{2}", source, msg, Environment.NewLine);
Console.ForegroundColor = originalForeGroundColor;
Console.BackgroundColor = originalBackGroundColor;
}
}
}
However, when those messages are displayed in a PowerShell console, they cannot be captured using redirection, like with Out-File, >&0 or even with Start-Transcript.
I have read about_Redirection, and using the redirect modifiers does not capture the console output. For instance, using a PowerShell Advanced Function (aka Cmdlet) I have written:
Get-CommandTrace -ScriptBlock { Get-Resource } *> C:\Temp\capture.log
The Get-CommandTrace Cmdlet sets the $VerbosePreference = 'Continue' during the ScriptBlock execution, and does capture the verbose from Get-Resource output there. But does not capture the Console output from my C# library.
So, my question is simply: Can a C# class that is not a Cmdlet class, nor inherited class, be able to write output to the existing PowerShell runspace it is being called from?
Note:
This is not a complete answer, because it has severe limitations - though it may work for specific use cases.
The original form of this answer used since-deprecated PowerShell SDK method .CreateNestedPipeline(), which cannot be used anymore if you're writing your code against the PowerShellStandard library for cross-platform and cross-edition compatibility (code that should run in both Windows PowerShell and PowerShell Core, on all supported platforms).
Chris (the OP) himself found a compatible alternative, which the current form of this answer is based on.
The challenge is to write to the invoking pipeline's output streams (as you've observed writing via Console is unrelated to PowerShell's output streams and prints directly to the console, with no ability to capture or redirect such output in PowerShell).
While I you can obtain a reference to the invoking runspace, there is no way I know of to obtain a reference to the running pipeline.
Using the invoking runspace you can write to PowerShell's output streams via a new pipeline, but that comes with severe limitations:
You cannot write to the caller's success output stream (1) that way; that is, while you can call cmdlets that target the other streams, such as Write-Verbose, Write-Output does not work.
Capturing the output from this nested pipeline in a variable or sending it through the pipeline requires enclosing the method call in (...) (or $(...) or #(...)) in addition to applying the appropriate redirection to the success output stream (e.g., 4>&1 for the verbose stream).
See the code comments for details.
Add-Type -TypeDefinition #'
using System.Management.Automation;
public class Write
{
public static void Verbose(string msg)
{
using (PowerShell ps = PowerShell.Create(RunspaceMode.CurrentRunspace)) {
// IMPORTANT: Use .AddScript(), not .AddCommand().
// Even though .AddCommand() + .AddParameter() is arguably a cleaner way to
// formulate the command, it results in output that cannot be captured.
ps.AddScript("Write-Output '" + msg.Replace("'", "''") + "'").Invoke();
}
}
}
'#
#"
$VerbosePreference = 'Continue'
# Regular output to the verbose stream.
Write-Verbose 'msg1'
# Verbose output via the custom type.
[Write]::Verbose('msg2')
# SUPPRESSING and REDIRECTING TO A FILE work.
[Write]::Verbose('msg3') 4> $null
[Write]::Verbose('msg4') 4> t.txt
# By default, REDIRECTING TO THE STANDARD OUTPUT STREAM (1)
# works only for the OUTSIDE, i.e. for CALLERS of this script.
[Write]::Verbose('msg5') 4>&1
# To REDIRECT TO THE STANDARD OUTPUT STREAM (1) in order to:
# * CAPTURE the result INSIDE your script
# * SEND THE RESULT THROUGH THE PIPELINE,
# additionally invoke the method call enclosed in (...) or $(...) or #(...)
$out = ([Write]::Verbose('msg6') 4>&1); "captured: [$out]"
([Write]::Verbose('msg7') 4>&1) | ForEach-Object { "piped: [$_]" }
While the answer above from mklement0 is a good one, it will not work if you attempt to use it with PowerShellCore or targeting .NetStandard 2.0 as the CreateNestedPipeline API is deprecated. (See this thread on the PowerShellStandard GitHub repo.)
So, instead, I have this working code:
Add-Type -TypeDefinition #'
using System.Management.Automation;
using System.Management.Automation.Runspaces;
public class Write
{
public static void Verbose(string msg)
{
using (PowerShell initialPowerShell = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
initialPowerShell.Commands.AddScript("Write-Verbose " + msg.Replace("\"", "\"\"") + "\" -v");
initialPowerShell.Invoke();
}
}
}
'#
$VerbosePreference = 'Continue'
# Regular output to the verbose stream.
Write-Verbose 'msg1'
# Verbose output via the custom type.
# !! This can NOT be redirected from the outside.
[Write]::Verbose('msg2')
# !! SUPPRESSING or REDIRECTING TO A FILE only works
# !! when DIRECTLY APPLIED to the method call.
[Write]::Verbose('msg3') 4> $null
[Write]::Verbose('msg4') 4> t.txt
# !! REDIRECTING TO THE STANDARD OUTPUT STREAM (1) for the OUTSIDE works,
# !! but obviously it then merges with success output.
[Write]::Verbose('msg5') 4>&1
# !! To REDIRECT TO THE STANDARD OUTPUT STREAM (1) and capture the result
# !! INSIDE your script, invoke the method call in (...) or $(...) or #(...)
$out = ([Write]::Verbose('msg6') 4>&1)
"[$out]"
Which works with PowerShell 5.1 for Windows, PowerShell Core 6.2.2 for Windows and Linux (Ubuntu/Debian).
I will still leave mklement0 reply marked as the answer to the original question. I'm just adding another one based on research I had compiled over the past few days on migrating my class library to .NetStandard 2.0.
I am writing a solution for users to open a file, and this file should navigate to a certain website and insert the user's username into the login form for them. This file needs to be accessed by users which are on a citrix session.
This should be extremely simple, and I have discovered a way of doing it via Powershell :
$aduser = Get-ADUser $env:USERNAME -Properties EmailAddress
$emailaddress = $aduser.EmailAddress
$url = "https://website.org/loginpage.asp"
$ie = New-Object -comobject "InternetExplorer.Application"
$ie.visible = $true
$ie.Navigate($url)
WaitForPage 10
$ie.Document.GetElementById("USERID").Value = $emailaddress
This works perfectly - it opens the web page, and inserts the username (email address).
However, when a user runs this from their machine, it seems impossible to hide either the CMD window (if running from .cmd or .bat) as well as the Powershell window. -WindowStyle Hidden just reduced the length of time the window appears for - which is not an acceptable solution.
So my next plan of action was to recreate the above code in c# and distribute it as an exe (as this is unlikely to show any console windows). However, I can't seem to find any method of doing this in C# which does not depend on external libraries (e.g. Selenium, which also requires a driver to be installed which is not a valid option for me).
I guess my question is - can the above Powershell script be recreated in C#? Is the -comobject from that script a .NET object, and if so how can I harness that in C#?
For reference - I am currently calling the .ps1 file as follows (in a CMD file) :
START Powershell.exe -WindowStyle Hidden -File \\file\Folder\SK\scripts\powershell\opensite.ps1
And I have not found any way of actually hiding the console windows which appear. I either need to find a solution to this, or a simple way of implementing the same thing in C#.
As I commented above, you could use VBScript or Jscript via wscript.exe to avoid launching another console window. Here's an example Jscript script, written as a batch + Jscript hybrid. Save it with a .bat extension, salt to taste, and give it a shot.
#if (#CodeSection == #Batch) #then
#echo off & setlocal
wscript /e:JScript "%~f0"
goto :EOF
#end // end batch / begin JScript hybrid code
var user = WSH.CreateObject("ADSystemInfo"),
email = GetObject("LDAP://" + user.UserName).EmailAddress,
url = "https://website.org/loginpage.asp",
ie = WSH.CreateObject('InternetExplorer.Application');
ie.visible = true;
ie.Navigate(url);
while (ie.readyState != 4) WSH.Sleep(25);
ie.document.getElementById('USERID').value = email;
if (ie.document.getElementById('password'))
ie.document.getElementById('password').focus();
It's actually a polyglot script, in that you can save it either with a .bat extension or .js. As .js, you can double-click it and it'll launch (assuming .js files are associated with wscript.exe, as they typically are by default) without any console window at all.
If you'd prefer an .exe file, the script above can be modified fairly easily into one that will self-compile and link a Jscript.NET executable. (This script still gets a .bat extension.)
#if (#CodeSection == #Batch) #then
#echo off & setlocal
for /f "delims=" %%I in ('dir /b /s "%windir%\microsoft.net\*jsc.exe"') do (
if not exist "%~dpn0.exe" "%%~I" /nologo /target:winexe /out:"%~dpn0.exe" "%~f0"
)
"%~dpn0.exe"
goto :EOF
#end // end batch / begin JScript.NET hybrid code
import System;
import System.Diagnostics;
try {
var wshShell:Object = new ActiveXObject("Wscript.Shell"),
user:Object = new ActiveXObject("ADSystemInfo"),
email:String = GetObject("LDAP://" + user.UserName).EmailAddress,
url:String = "https://website.org/loginpage.asp",
ie:Object = new ActiveXObject('InternetExplorer.Application');
}
catch(e:Exception) { System.Environment.Exit(1); }
ie.visible = true;
ie.Navigate(url);
// force IE window to the foreground and give it focus
var proc:System.Diagnostics.Process[] = System.Diagnostics.Process.GetProcesses();
for (var i:Int16 = proc.length, hwnd:IntPtr = IntPtr(ie.hwnd); i--;) {
if (proc[i].MainWindowHandle === hwnd && wshShell.AppActivate(proc[i].Id)) break;
}
while (ie.readyState != 4) System.Threading.Thread.Sleep(25);
ie.document.getElementById('USERID').value = email;
if (ie.document.getElementById('password'))
ie.document.getElementById('password').focus();
You can certainly call COM objects in C#, as explained in this existing answer:
If the library is already registered, you can perform the following
steps to have Visual Studio generate an interop assembly for you:
Open to your Visual Studio project.
Right click on 'References' (right under the project in your Solution Explorer) and select 'Add Reference'.
Select the COM tab.
Select the Component you wish to interop with.
Select 'ok'.
This will be a class or set of C# classes that wrap all of the COM
interface stuff with a normal C# class. Then you just use it like any
other C# library. If the import of the reference worked well, you can
explore it like any other reference and the
methods/structs/classes/constants should show up in that namespace and
intellisense.
Alternatively, you could execute the PowerShell within C# using a Runspace and a Pipeline. See Runspace samples on MSDN (here's example 3 from the link):
namespace Microsoft.Samples.PowerShell.Runspaces
{
using System;
using System.Collections;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using PowerShell = System.Management.Automation.PowerShell;
/// <summary>
/// This class contains the Main entry point for this host application.
/// </summary>
internal class Runspace03
{
/// <summary>
/// This sample shows how to use the PowerShell class to run a
/// script that retrieves process information for the list of
/// process names passed to the script. It shows how to pass input
/// objects to a script and how to retrieve error objects as well
/// as the output objects.
/// </summary>
/// <param name="args">Parameter not used.</param>
/// <remarks>
/// This sample demonstrates the following:
/// 1. Creating a PowerSHell object to run a script.
/// 2. Adding a script to the pipeline of the PowerShell object.
/// 3. Passing input objects to the script from the calling program.
/// 4. Running the script synchronously.
/// 5. Using PSObject objects to extract and display properties from
/// the objects returned by the script.
/// 6. Retrieving and displaying error records that were generated
/// when the script was run.
/// </remarks>
private static void Main(string[] args)
{
// Define a list of processes to look for.
string[] processNames = new string[]
{
"lsass", "nosuchprocess", "services", "nosuchprocess2"
};
// The script to run to get these processes. Input passed
// to the script will be available in the $input variable.
string script = "$input | get-process -name {$_}";
// Create a PowerShell object. Creating this object takes care of
// building all of the other data structures needed to run the script.
using (PowerShell powershell = PowerShell.Create())
{
powershell.AddScript(script);
Console.WriteLine("Process HandleCount");
Console.WriteLine("--------------------------------");
// Invoke the script synchronously and display the
// ProcessName and HandleCount properties of the
// objects that are returned.
foreach (PSObject result in powershell.Invoke(processNames))
{
Console.WriteLine(
"{0,-20} {1}",
result.Members["ProcessName"].Value,
result.Members["HandleCount"].Value);
}
// Process any error records that were generated while running
// the script.
Console.WriteLine("\nThe following non-terminating errors occurred:\n");
PSDataCollection<ErrorRecord> errors = powershell.Streams.Error;
if (errors != null && errors.Count > 0)
{
foreach (ErrorRecord err in errors)
{
System.Console.WriteLine(" error: {0}", err.ToString());
}
}
}
System.Console.WriteLine("\nHit any key to exit...");
System.Console.ReadKey();
}
}
}
While the second approach is no doubt going to take longer, it may make sense if you want to keep the PowerShell code out of the executable so that you can more easily change it, without having to recompile every time.
Essentially, the exe could just be used to execute a given powershell script invisibly.
I was using the below C# until I properly understood rojo's answer. This method does work, but is not as simple/elegant as rojo's so I marked that as the answer.
static class Program
{
private static void Main()
{
var startInfo = new ProcessStartInfo(#"\\file\administration\Unused\Apps\opencascade\Alternate.bat")
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
CreateNoWindow = true
};
Process.Start(startInfo);
var startInfo = new ProcessStartInfo("Wscript.exe", #"\\file\administration\Unused\Apps\opencascade\Alternate.js")
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
CreateNoWindow = true
};
Process.Start(startInfo);
var startInfo = new ProcessStartInfo("Powershell.exe",
#"\\file\administration\Unused\Apps\opencascade\opencascade.ps1")
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
CreateNoWindow = true
};
Process.Start(startInfo);
}
}
Note that the main method here is running 3 different solutions, one after each other. The different "versions" are each dependant on whether it makes more sense in the environment to use .bat or .ps1 or .js. I will be using the .js version, which works in this context and the executable generated by this C# code hides the resulting console window.
I'm using code from this post over at Microsoft's TechNet to change the icon of my running PowerShell application. This works great for the icon that is displayed in the Powershell window itself, but it doesn't change the Taskbar's icon. I changed the function a bit an hoped that it would also change the icon displayed in the Taskbar.
# Set the icon of the current console window to the specified icon.
#
# AUTHOR: Aaron Lerch <http://www.aaronlerch.com/blog>
# COPYRIGHT: © 2009 Aaron Lerch
# LINK: http://gallery.technet.microsoft.com/scriptcenter/9d476461-899f-4c98-9d63-03b99596c2c3
#
# PARAM:
# -IconFile
# Absolute path to the icon file.
# RETURN:
# $null
function Set-ConsoleIcon {
Param(
[parameter(Mandatory = $true)] [string] $IconFile
)
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | Out-Null
# Verify the file exists
if ([System.IO.File]::Exists($iconFile) -eq $true) {
$ch = Invoke-Win32 'kernel32' ([IntPtr]) 'GetConsoleWindow'
$i = 0;
$size = 16;
while ($i -ne 4) {
$ico = New-Object System.Drawing.Icon($iconFile, $size, $size)
if ($ico -ne $null) {
Send-Message $ch 0x80 $i $ico.Handle | Out-Null
}
if ($i -eq 4) {
break
}
$i += 1
$size += 16
}
}
else {
Write-Host 'Icon file not found' -ForegroundColor 'Red'
}
}
I'm providing the icon in the sizes 16 (wParam 1), 32 (wParam 2), 48 (wParam 3), and 64 (wParam 4).
I also tried to change the Icon from my launching C# application (based on this Stackoverflow discussion) but that didn't work at all.
If you'd like to see the complete code have a look at the following:
the C# class that starts the Powershell process
the Powershell PS1 script
This might not be possible. Here are more details about the "group icon" in the taskbar:
Change icon of group in taskbar (Win7)
Update:
You can change the application ID of your window. Since the icon primarily comes from the application ID, by changing it Explorer doesn't know the default icon anymore and will use the actual window icon. This also ungroups the window from other CMD windows to make the individual icon visible at all. (There's a taskbar animation like for closed/new windows when you do this in an existing console window.) There's an MSDN article, look for "application ID" in it:
https://msdn.microsoft.com/en-us/magazine/dd942846.aspx
Here's the relevant code from it (C++):
#define WINVER 0x601
#define _WIN32_WINNT 0x601
#include <Propvarutil.h>
#include <propkey.h>
#include <Shellapi.h>
PROPVARIANT pv;
InitPropVariantFromString(L"MyAppID", &pv);
IPropertyStore* pps;
VERIFY(SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&pps)));
VERIFY(pps->SetValue(PKEY_AppUserModel_ID, pv));
VERIFY(pps->Commit());
(Linked libs: shlwapi.lib)
The Windows API Code Pack should also have managed wrapper code for this. Didn't look it up because I currently use this function in a C++ application. But I found other questions about it here.
For your PowerShell script that probably won't help much either. Since it's all native code wrapped with more complex managed code, I think your best bet would be a little native helper tool. I am currently integrating this function into my FlashConsoleWindow tool that can do some more things to console windows like flashing or displaying a taskbar progress state.
An easy alternative is to create a shortcut to the powershell exe. Change the icon of the shortcut to whatever you want.
Then whenever you call your script, use the shortcut instead of the PS exe. So instead of
powershell.exe -ExecutionPolicy Bypass -File D:\scripts\whatever.ps1
Use
D:\scripts\powershell.lnk -ExecutionPolicy Bypass -File D:\scripts\whatever.ps1
The shortcut powershell.lnk can be "C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe" or just powershell.exe without the full path (as long as PATH var is set properly for PS on the system)
You can put the PS shortcut anywhere really, but I would recommend you put it with the script for portability.
Now when you launch the script via the shortcut, the taskbar icon will be whatever you defined it as via the shortcut file.
The only caveat is that your window settings in the shortcut will override the PS window, so you should define how you want it to look in the shortcut properties.
Is it possible to make an application in C# that will be able to delete itself in some condition.
I need to write an updater for my application but I don't want the executable to be left after the update process.
There is an official .Net OneClick but due to some incompatibilities with my HTTP server and some problems of OneClick itself I'm forced to make one myself.
George.
[EDIT]
In more details:
I have:
Application Executable which downloads the updater ("patch", but not exactly) this "patch" updates the application executable itself.
Application executes as folowed:
Application: Start -> Check Version -> Download new Updater -> Start Updater -> exit;
Updater: Start -> do it's work -> start Application Executable -> self delete (this is where I get stuck);
If you use Process.Start you can pass in the Del parameter and the path to the application you wish to delete.
ProcessStartInfo Info=new ProcessStartInfo();
Info.Arguments="/C choice /C Y /N /D Y /T 3 & Del "+
Application.ExecutablePath;
Info.WindowStyle=ProcessWindowStyle.Hidden;
Info.CreateNoWindow=true;
Info.FileName="cmd.exe";
Process.Start(Info);
Code snippet taken from this article
I suggest you use a batch file as a bootstrap and have it delete itself and the exe afterwards
public static class Updater
{
public static void Main()
{
string path = #"updater.bat";
if (!File.Exists(path))
{
// Create a file to write to.
using (StreamWriter sw = File.CreateText(path))
{
sw.WriteLine("updater.exe");
sw.WriteLine("delete updater.exe /y");
sw.WriteLine("delete updater.bat /y");
}
System.Process.Start(path);
}
else
{
RunUpdateProcess();
}
}
private void RunUpdateProcess()
{
.....
}
}
It's tricky without introducing yet another process (that you'd then want to delete as well, no doubt). In your case, you already have 2 processes - updater.exe and application.exe. I'd probably just have the Application delete updater.exe when it's spawned from there - you could use a simple command line arg, or an IPC call from updater.exe to application.exe to trigger it. That's not exactly a self deleting EXE, but fulfills the requirements I think.
For the full treatment, and other options you should read the definitive treatment of self deleting EXEs. Code samples are in C (or ASM), but should be p/invokable.
I'd probably try CreateFile with FILE_FLAG_DELETE_ON_CLOSE for updater.exe with something like (psuedo code):
var h = CreateFile(
"updater.exe",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE
);
byte[] updaterBytes = GetUpdaterBytesFromWeb();
File.WriteAllBytes("updater.exe", updaterBytes);
Process.Start("updater.exe");
Once application.exe exits, updater.exe has a file handle of 1. When updater.exe exits, it drops to 0 and should be deleted.
Couldn't you simply delete the updater from within the application? i.e.:
Application: Start -> [Delete old updater if present] -> Check version -> Download new updater -> Start updater -> exit;
Updater: Start -> Perform update -> Start application -> exit;
Application: Start -> [Delete old updater if present] -> ...
Mhh so let me get this straight. You got some application.exe and your updater application updater.exe?
So when you start your application.exe it checks some webserver for a newer version and then starts updater.exe. And you want updater.exe to delete itself after it has finished updating? Or do you want to delete the downloaded patch (or similar)? Please be a bit more precise.
Consider that when you are deleting updater.exe you must recreate it for the next update process.
your second line can be
Updater: Star -> do it's work -> start Application Executable -> Updater Exits -> Application deletes your Updater.exe
public void uninstall() {
string app_name = Application.StartupPath + "\\" + Application.ProductName + ".exe";
string bat_name = app_name + ".bat";
string bat = "#echo off\n"
+ ":loop\n"
+ "del \"" + app_name + "\"\n"
+ "if Exist \"" + app_name + "\" GOTO loop\n"
+ "del %0";
StreamWriter file = new StreamWriter(bat_name);
file.Write(bat);
file.Close();
Process bat_call = new Process();
bat_call.StartInfo.FileName = bat_name;
bat_call.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
bat_call.StartInfo.UseShellExecute = true;
bat_call.Start();
Application.Exit();
}
self delete by an external executable file ".bat" for windows form applications.