Powershell change taskbar icon of running console application - c#

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.

Related

How to apply windows 11 taskbar layout without restarting Explorer?

I'm trying to make a program to switch my taskbar to match my virtual desktop in Windows 11. For instance, I'd have a Desktop named "3d Modeling" where I'd have Blender, Meshmixer, etc., pinned to the taskbar, but when I switch to the "3d Printing" Desktop then the taskbar would have SuperSlicer, Lychee, etc., pinned instead.
I have most of this working using an XML layout file, and using some powershell and the VirtualDesktop module to import the layout that matches the current desktop. The layout will be imported, but the taskbar won't reflect that change unless explorer.exe is restarted. But, when I restart Explorer, the program starts throwing a bunch of errors, I'm assuming because the module is unable to reconnect to the new Explorer process. The program works as expected other than the taskbar layouts not visually displaying (desktop switch is detected, new layout is selected properly, and layout is imported successfully) if Explorer is not restarted.
I would much rather not have to fully restart Explorer since that causes a bunch of stuff to disappear from the screen. I have gone down the rabbit hole a bit trying to get this to work using some C#, specifically following this thread and poking through the code for the AdaptiveTaskbar module. Both of those are older than Win11, so I'm thinking something may have changed since then. So far I haven't managed to get it to refresh the taskbar at all, and I'm not experienced enough with the Windows API to know where to look next.
So my question is: is there a way to do this in Win11? It doesn't necessarily have to be a C# or Powershell solution. Here's my most recent code, the relevant parts taken from AdaptiveTaskbar in case there's maybe a small thing I'm missing. I have also tried it using SendMessageTimeout.
Import-Module VirtualDesktop
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition #"
[DllImport("User32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern bool SendNotifyMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam);
"#
$lastDesktopIndex = -1
$currentDesktopIndex = 0
$currentDesktopName = ''
while ($True) {
$currentDesktopIndex = Get-DesktopIndex -Desktop $(Get-CurrentDesktop)
$currentDesktopName = Get-DesktopName -Desktop $(Get-CurrentDesktop)
if ($lastDesktopIndex -ne $currentDesktopIndex) {
Import-StartLayout -LayoutPath "$PSScriptRoot\taskbars\$currentDesktopName.xml" -MountPath "C:\"
Write-Output "$currentDesktopIndex : $currentDesktopName : $PSScriptRoot\taskbars\$currentDesktopName.xml"
# Stop-Process -name explorer
# Start-Process -FilePath "explorer.exe"
[void] ([Win32.Nativemethods]::SendNotifymessage([IntPtr]0xffff, 0x1a, [IntPtr]::Zero, "TraySettings"))
}
$lastDesktopIndex = $currentDesktopIndex
#Start-Sleep -Seconds 1
Read-Host -Prompt "Press any key to continue"
}

How to enumerate the real Windows File Explorer windows

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.

Pinning Metro Apps To Taskbar Windows 10 Powershell

The following code will pin a metro app to start given an AUMID
If you change
-match 'Pin To Start'
Unfortunately changing the match to 'Pin To Taskbar' does not work. What is going on here?
function Pin-Taskbar { param(
[string]$aumid,
[switch]$unpin
)
try{
if ($unpin.IsPresent){
((New-Object -Com Shell.Application).NameSpace('shell:::{4234d49b-0245-4df3-b780-3893943456e1}').Items() | ?{$_.Path -eq $aumid}).Verbs() | ?{$_.Name.replace('&','') -match 'Unpin from Taskbar'} | %{$_.DoIt()}
return "App '$aumid' unpinned from Taskbar"
}else{
((New-Object -Com Shell.Application).NameSpace('shell:::{4234d49b-0245-4df3-b780-3893943456e1}').Items() | ?{$_.Path -eq $aumid}).Verbs() | ?{$_.Name.replace('&','') -match 'Pin to Taskbar'} | %{$_.DoIt()}
return "App '$aumid' pinned to Taskbar"
}
}catch{
Write-Error "Error Pinning/Unpinning App! (App-Name correct?)"
}
}
Pin-Taskbar king.com.CandyCrushSaga_kgqvnymyfvs32!App
I assume you are a good citizen and do not want to spam the taskbar of the users.
In previous versions of Windows, you were able to use the verb Pintotaskbar to programmatically un-/pin programs to your taskbar.
$shell = new-object -com "Shell.Application" 
$folder = $shell.Namespace((Join-Path $env:SystemRoot System32\WindowsPowerShell\v1.0))
$item = $folder.Parsename('powershell_ise.exe')
$item.invokeverb('taskbarpin');
This no longer works in Windows 10. The verb Pintotaskbar simply is not exist accessible anymore.
Hence, an unapproved 3rd-party command prompt utility
called SysPin was brought to life - that allows you to do exactly what you want (though it does not work with each and every app, especially UWP/Metro apps).
However, since Windows 10 version 1607 there is a new official way: adding a <TaskbarLayout>
See: Customize Pinned Items on Taskbar in Windows 10 1607 during OSD with ConfigMgr

How do I detect if WinPE(4) has booted from a UEFI or BIOS?

I am looking for a way to reliably detect when I boot into WinPE 4 (powershell) (or WinPE 3 (vbs) as an alternative), have I booted from a UEFI or BIOS System? (without running a third party exe as I am in a restricted environment)
This significantly changes how I would be partitioning a windows deployment as the partitions layout changes and format. (GPT vs. MBR, etc)
I have one working that is an adaptation of this C++ code in powershell v3 but it feels pretty hack-ish :
## Check if we can get a dummy flag from the UEFI via the Kernel
## [Bool] check the result of the kernel's fetch of the dummy GUID from UEFI
## The only way I found to do it was using the C++ compiler in powershell
Function Compile-UEFIDectectionClass{
$win32UEFICode= #'
using System;
using System.Runtime.InteropServices;
public class UEFI
{
[DllImport("kernel32.dll")]
public static extern UInt32 GetFirmwareEnvironmentVariableA([MarshalAs(UnmanagedType.LPWStr)] string lpName, [MarshalAs(UnmanagedType.LPWStr)] string lpGuid, IntPtr pBuffer, UInt32 nSize);
public static UInt32 Detect()
{
return GetFirmwareEnvironmentVariableA("", "{00000000-0000-0000-0000-000000000000}", IntPtr.Zero, 0);
}
}
'#
Add-Type $win32UEFICode
}
## A Function added just to check if the assembly for
## UEFI is loaded as is the name of the class above in C++.
Function Check-IsUEFIClassLoaded{
return ([System.AppDomain]::CurrentDomain.GetAssemblies() | % { $_.GetTypes()} | ? {$_.FullName -eq "UEFI"}).Count
}
## Just incase someone was to call my code without running the Compiled code run first
If (!(Check-IsUEFIClassLoaded)){
Compile-UEFIDectectionClass
}
## The meat of the checking.
## Returns 0 or 1 ([BOOL] if UEFI or not)
Function Get-UEFI{
return [UEFI]::Detect()
}
This seems pretty over the top just to get a simple flag.
Does anyone know if there is there a better way to get this done?
It is no less hacky, in the sense it will still require interop from powershell, but the interop code might be neater if you use (or can call): GetFirmwareType().
This returns a FIRMWARE_TYPE enumeration documented here. I can't believe given both functions are introduced in Windows 8 and exported by kernel32.dll that Microsoft's own documentation points you at "using a dummy variable"!
Internally, GetFirmwareType calls NtQuerySystemInformation. I will dig into what it is doing, but I do not think it is necessarily going to be enlightening.
Unfortunately, this only works for PE4 (Windows 8) since these functions were only added then.
The easieast way by far is to run on PowerShell:
$(Get-ComputerInfo).BiosFirmwareType
This may be a little late, but if one knows they are running in WinPE, the following code should work:
$isuefi = (Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Control).PEFirmwareType -eq 2
$env:firmware_type
Not sure since what version this is supported.
Returns UEFI and Legacy in my tests.
However this is on full installation, Have note confirmed existence in WinPE
It looks like the PE environment has a folder that is specific to the PE environment. In addition, the variable %TargetDir% is described here, TARGETDIR property.
Lastly, you could check if you are running from X: There should be also a folder that has the boot.wim image you can check for. I believe the path would be X:\Sources\Boot.wim but double check.
if ( Test-Path "%TargetDir%\Windows\wpeprofiles" ) {
Write-host "You're in Windows PE"
}
I don't know if this will help (based on C# solution), but:
Win32_DiskPartition has the properties "Bootable" (bool), "BootPartition" (bool), and "Type" (string). For my UEFI system, "Type" comes back as the string "GPT: System".
Now, for all Win32_DiskPartitions that are bootable, are a boot partition, and have the specified type, determine if any of them are internal.
Hope this helps.

Send Text in Clipboard to Application like Notepad (C# or Powershell)

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)

Categories

Resources