Find/Kill 64 bit processes by path in 32 bit powershell - powershell

I have PowerShell cleanup script for an application. Ideally processes are discovered by their path, so as not to recklessly kill other processes on the system which may have similar names. We noticed some processes are not being detected/killed, and after much experimentation realized the bit-ness is the issue. The script is bootstrapped in 32-bit for compatibility, but some of the processes are not.
Get-Process can be called in 32-bit PowerShell and returns all the processes including the 64 bit ones, however as noted in This Ref:
On computers that are running a 64-bit version of Windows, the 64-bit version of PowerShell gets only 64-bit process modules and the 32-bit version of PowerShell gets only 32-bit process modules.
And indeed while the processes are discovered, the process module information (including the Path of the process) is not available for processes whose bit-ness does not match the shell.
This question has some discussion about it: How can I get the executable path of a 64-bit process given its PID from a 32-bit process?
The suggested Get-WmiObject query does not work for me as shown, it returns 64- bit processes with missing ExecutablePath information, basically the same as Get-Process.
So my question is: Is it possible to call the WinAPI functions like QueryFullProcessImageName() or GetModuleFileNameEx() from a PowerShell script as a workaround to get this information? Or is there any other way to do this I am not considering?

In order to meet this need this is what I cobbled together. Maybe it will help someone else. Criticism welcome.
$pinvoke = Add-Type -PassThru -Name pinvoke -MemberDefinition #'
[DllImport("kernel32.dll", SetLastError=true)]
private static extern bool CloseHandle(
IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(
uint processAccess,
bool bInheritHandle,
int processId);
[DllImport("kernel32.dll", SetLastError=true)]
private static extern bool QueryFullProcessImageName(
IntPtr hProcess,
int dwFlags,
System.Text.StringBuilder lpExeName,
ref int lpdwSize);
private const int QueryLimitedInformation = 0x00001000;
public static string GetProcessPath(int pid)
{
var size = 1024;
var sb = new System.Text.StringBuilder(size);
var handle = OpenProcess(QueryLimitedInformation, false, pid);
if (handle == IntPtr.Zero) return null;
var success = QueryFullProcessImageName(handle, 0, sb, ref size);
CloseHandle(handle);
if (!success) return null;
return sb.ToString();
}
'#
And then you can get the path of a 32 or 64 bit process by
$pinvoke::GetProcessPath($pid)
To filter processes instead of:
Get-Process | Where-Object {$_.Path -like "*$filePath*"}
which has the problems with 32/64 bit, you can use
Get-Process | Where-Object {$pinvoke::GetProcessPath($_.Id) -like "*$filePath*"}
Pipe that into Stop-Process or whatever you want to do with it.

Related

Changing Windows 10 cursor icon with Powershell without reseting

I'm making a startup script for Windows and have been wanting to change my cursor icons. However, I want to do it without a computer reset and through powershell.
Set-ItemProperty -Path "HKCU:\Control Panel\Cursors\Arrow" -Value
"F:\nutty-squirrels\callmezippy_squirrelUnavailble.cur"
I know that it's possible to change the cursor icon without reset using the GUI, but I can't seem to get it to work using scripts, as regedit does not update the cursor (or, at least, it hasn't through my testing.)
I'm thinking that reseting some process would allow the cursor changes to occur, but I have no idea what that process is. If anyone has any idea, it would be a great help!
The Set-ItemProperty call is missing the -Name argument and you need to call the WinAPI function SystemParametersInfo to notify the system about the settings change:
# Define a C# class for calling WinAPI.
Add-Type -TypeDefinition #'
public class SysParamsInfo {
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, uint pvParam, uint fWinIni);
const int SPI_SETCURSORS = 0x0057;
const int SPIF_UPDATEINIFILE = 0x01;
const int SPIF_SENDCHANGE = 0x02;
public static void CursorHasChanged() {
SystemParametersInfo(SPI_SETCURSORS, 0, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
}
'#
# Change the cursor
Set-ItemProperty -Path 'HKCU:\Control Panel\Cursors' -Name 'Arrow' -Value '%SystemRoot%\cursors\aero_arrow_xl.cur'
# Notify the system about settings change by calling the C# code
[SysParamsInfo]::CursorHasChanged()

How to stop a windows 10 machine from sleeping/hibernating down when running a powershell process?

I have a powershell process that reads records from a remote server and copies them into a local database. When it runs, it might run for 8-12 hours.
How do I prevent the computer from shutting down (or going into sleep/hibernate mode) during this time? I know I can adjust the 'Power and sleep settings' to set the computer to never sleep, but that's not what I'm looking for - I do want it to go to sleep when the process isn't running.
I know that sleep/hibernate is suspended if a netflix or youtube video is running, I'd like the computer to do the same when a powershell process is running.
The powershell process runs in a command window on the desktop - I'm happy for the screen saver to activate, but what I don't want to happen is for me to wake the machine after 8 hours and discover that the process only ran for 10 minutes before the computer went to sleep!
With some extra effort, you can achieve the desired behavior with the standard powercfg.exe utility, by using a custom, always-on power scheme that is created on demand and temporarily activated for the duration of your script run:
Note:
Look for comment # YOUR CODE GOES HERE below.
For a .NET / Windows API-based alternative, see this answer.
# Define the properties of a custom power scheme, to be created on demand.
$schemeGuid = 'e03c2dc5-fac9-4f5d-9948-0a2fb9009d67' # randomly created with New-Guid
$schemeName = 'Always on'
$schemeDescr = 'Custom power scheme to keep the system awake indefinitely.'
# Helper function that ensures that the most recent powercfg.exe call succeeded.
function assert-ok { if ($LASTEXITCODE -ne 0) { throw } }
# Determine the currently active power scheme, so it can be restored at the end.
$prevGuid = (powercfg -getactivescheme) -replace '^.+([-0-9a-f]{36}).+$', '$1'
assert-ok
# Temporarily activate a custom always-on power scheme; create it on demand.
try {
# Try to change to the custom scheme.
powercfg -setactive $schemeGuid 2>$null
if ($LASTEXITCODE -ne 0) { # Changing failed -> create the scheme on demand.
# Clone the 'High performance' scheme.
$null = powercfg -duplicatescheme SCHEME_MIN $schemeGuid
assert-ok
# Change its name and description.
$null = powercfg -changename $schemeGuid $schemeName $schemeDescr
# Activate it
$null = powercfg -setactive $schemeGuid
assert-ok
# Change all settings to be always on.
# Note:
# * Remove 'monitor-timeout-ac', 'monitor-timeout-dc' if it's OK
# for the *display* to go to sleep.
# * If you make changes here, you'll have to run powercfg -delete $schemeGuid
# or delete the 'Always on' scheme via the GUI for changes to take effect.
# * On an AC-only machine (desktop, server) the *-ac settings aren't needed.
$settings = 'monitor-timeout-ac', 'monitor-timeout-dc', 'disk-timeout-ac', 'disk-timeout-dc', 'standby-timeout-ac', 'standby-timeout-dc', 'hibernate-timeout-ac', 'hibernate-timeout-dc'
foreach ($setting in $settings) {
powercfg -change $setting 0 # 0 == Never
assert-ok
}
}
# YOUR CODE GOES HERE.
# In this sample, wait for the user to press Enter before exiting.
# Before that, the 'Always on' power scheme should remain in
# effect, and the machine shouldn't go to sleep.
pause
} finally { # Executes even when the script is aborted with Ctrl-C.
# Reactivate the previously active power scheme.
powercfg -setactive $prevGuid
}
You could create a wrapper script from the above, to which you pass the path of the script to execute.
If you don't mind modifying the currently active scheme, you can use the approach shown in Kerr's answer, using per-setting powercfg -change <setting> <value-in-minutes> calls (/x / -x is an alias of /change / -change), using one of the following <setting> names in each call; passing 0 as <value-in-minutes> represents never:
monitor-timeout-ac
monitor-timeout-dc
disk-timeout-ac
disk-timeout-dc
standby-timeout-ac
standby-timeout-dc
hibernate-timeout-ac
hibernate-timeout-dc
Note, however, that such changes are persistent, so you may want to restore the original values later, which takes extra effort.
To offer a .NET / Windows API-based alternative to the powercfg.exe-based solution:
Note:
The solution uses Add-Type to compile C# code on demand, which incurs a performance penalty the first time the code is called in the current session.
It is important to call ::StayAwake($false) in the same session in order to clear the power requests made.
Look for comment # YOUR CODE GOES HERE below.
This solution was adapted from this C# answer by MarkusEgle.
Add-Type -ErrorAction Stop -Name PowerUtil -Namespace Windows -MemberDefinition #'
// Member variables.
static IntPtr _powerRequest;
static bool _mustResetDisplayRequestToo;
// P/Invoke function declarations.
[DllImport("kernel32.dll")]
static extern IntPtr PowerCreateRequest(ref POWER_REQUEST_CONTEXT Context);
[DllImport("kernel32.dll")]
static extern bool PowerSetRequest(IntPtr PowerRequestHandle, PowerRequestType RequestType);
[DllImport("kernel32.dll")]
static extern bool PowerClearRequest(IntPtr PowerRequestHandle, PowerRequestType RequestType);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
static extern int CloseHandle(IntPtr hObject);
// Availablity Request Enumerations and Constants
enum PowerRequestType
{
PowerRequestDisplayRequired = 0,
PowerRequestSystemRequired,
PowerRequestAwayModeRequired,
PowerRequestMaximum
}
const int POWER_REQUEST_CONTEXT_VERSION = 0;
const int POWER_REQUEST_CONTEXT_SIMPLE_STRING = 0x1;
// Availablity Request Structures
// Note: Windows defines the POWER_REQUEST_CONTEXT structure with an
// internal union of SimpleReasonString and Detailed information.
// To avoid runtime interop issues, this version of
// POWER_REQUEST_CONTEXT only supports SimpleReasonString.
// To use the detailed information,
// define the PowerCreateRequest function with the first
// parameter of type POWER_REQUEST_CONTEXT_DETAILED.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct POWER_REQUEST_CONTEXT
{
public UInt32 Version;
public UInt32 Flags;
[MarshalAs(UnmanagedType.LPWStr)]
public string SimpleReasonString;
}
/// <summary>
/// Prevents the system from going to sleep, by default including the display.
/// </summary>
/// <param name="enable">
/// True to turn on, False to turn off. Passing True must be paired with a later call passing False.
/// If you pass True repeatedly, subsequent invocations take no actions and ignore the parameters.
/// If you pass False, the remaining paramters are ignored.
// If you pass False without having passed True earlier, no action is performed.
//// </param>
/// <param name="includeDisplay">True to also keep the display awake; defaults to True.</param>
/// <param name="reasonString">
/// A string describing why the system is being kept awake; defaults to the current process' command line.
/// This will show in the output from `powercfg -requests` (requires elevation).
/// </param>
public static void StayAwake(bool enable, bool includeDisplay = true, string reasonString = null)
{
if (enable)
{
// Already enabled: quietly do nothing.
if (_powerRequest != IntPtr.Zero) { return; }
// Configure the reason string.
POWER_REQUEST_CONTEXT powerRequestContext;
powerRequestContext.Version = POWER_REQUEST_CONTEXT_VERSION;
powerRequestContext.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
powerRequestContext.SimpleReasonString = reasonString ?? System.Environment.CommandLine; // The reason for making the power request.
// Create the request (returns a handle).
_powerRequest = PowerCreateRequest(ref powerRequestContext);
// Set the request(s).
PowerSetRequest(_powerRequest, PowerRequestType.PowerRequestSystemRequired);
if (includeDisplay) { PowerSetRequest(_powerRequest, PowerRequestType.PowerRequestDisplayRequired); }
_mustResetDisplayRequestToo = includeDisplay;
}
else
{
// Not previously enabled: quietly do nothing.
if (_powerRequest == IntPtr.Zero) { return; }
// Clear the request
PowerClearRequest(_powerRequest, PowerRequestType.PowerRequestSystemRequired);
if (_mustResetDisplayRequestToo) { PowerClearRequest(_powerRequest, PowerRequestType.PowerRequestDisplayRequired); }
CloseHandle(_powerRequest);
_powerRequest = IntPtr.Zero;
}
}
// Overload that allows passing a reason string while defaulting to keeping the display awake too.
public static void StayAwake(bool enable, string reasonString)
{
StayAwake(enable, false, reasonString);
}
'#
try {
# Create power request(s) that keep the system awake.
# Pass $false as the 2nd argument to allow the display to go to sleep.
# The reason string is visible when you run `powercfg.exe -requests` to show current requests
# (requires elevation).
# Defaults: keep the display awake too, use the current process' command line as the reason string.
[Windows.PowerUtil]::StayAwake($true, $true, "Running long-running script $PSCommandPath.")
# YOUR CODE GOES HERE.
# In this sample, wait for the user to press Enter before exiting.
# Before that, the system should stay awake indefinitely.
pause
} finally { # This ensures that the previous scheme is restored even when the script is aborted with Ctrl-C.
# Clear the power requests.
[Windows.PowerUtil]::StayAwake($false)
}
Simple one-liner that I use:
Powercfg /x -standby-timeout-ac 0

Change and update the size of the cursor in Windows 10 via PowerShell

I've written the code below to affect (what I think) are the only reg keys responsible for the size of the cursor and pointer in Windows 10.
Here's the code I have so far (Some additional comments within):
$RegConnect = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]"CurrentUser", "$env:COMPUTERNAME")
$RegCursorsAccess = $RegConnect.OpenSubKey("Software\Microsoft\Accessibility", $true)
$RegCursorsControlPanel = $RegConnect.OpenSubKey("Control Panel\Cursors", $true)
# In the code below I'm trying to change the size of the cursor.
$RegCursorsControlPanel.SetValue("CursorBaseSize", 48)
$RegCursorsAccess.SetValue("CursorSize", 3)
$RegCursorsAccess.Close()
$RegConnect.Close()
# This section is where I thought it would update the cursor size.
# Here is where it lists stuff relating to setting and updating any settings changed.
# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
# SPI_SETCURSORS
# 0x0057
# Reloads the system cursors. Set the uiParam parameter to zero and the pvParam parameter to NULL.
$CSharpSig = #'
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(
uint uiAction,
uint uiParam,
uint pvParam,
uint fWinIni);
'#
$CursorRefresh = Add-Type -MemberDefinition $CSharpSig -Name WinAPICall -Namespace SystemParamInfo -PassThru
$CursorRefresh::SystemParametersInfo(0x0057,0,$null,0)
It will change the correct values in the registry.
So if I run this PowerShell code the mouse size in the ease of access setting is at the correct value.
But the cursor doesn't update.
How is it possible to force the update without logging out and back in or restarting the machine.
Here are some related MS links:
WM_SETTINGCHANGE message
SystemParametersInfoA function
EDIT - Some additional info
If I run Process Monitor from Sysinternals and dig deep in there I can see this under the stack summary.
This may lead someone more knowledgeable than me to find how to update the mouse size.
The HKCU\Control Panel\Cursors\(Default) section SettingsHandlers_nt.dll
And this also for the accessibility section. Windows.UI.Accessibility.dll
Here are the settings I used in Process Monitors filter to narrow down the items.
So after a bit of hacking about SystemSettings.exe with cheat engine I found how MS sets the cursor size. It ends up still using SystemParametersInfo but with some undocumented arguments. Try the following :)
SystemParametersInfo(0x2029, 0, 16, 0x01);
to set a cursor size of 16. yup you can go below their minimum of 32, all the way down to 1 :)
But the cursor doesn't update.
After registry value changed, it requires trigger to apply these update.
It can be done using SystemParametersInfo function with SPIF_UPDATEINIFILE and SPIF_SENDCHANGE to writes the new system-wide parameter setting to the user profile and broadcasts the WM_SETTINGCHANGE message after updating the user profile.
SystemParametersInfo(SPI_SETCURSORS, 0, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
The following is a PowerShell command example:
$RegConnect = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]"CurrentUser","$env:COMPUTERNAME")
$RegCursors = $RegConnect.OpenSubKey("Control Panel\Cursors",$true)
$RegCursors.SetValue("","Windows Black")
$RegCursors.SetValue("CursorBaseSize",0x40)
$RegCursors.SetValue("AppStarting","%SystemRoot%\cursors\wait_r.cur")
$RegCursors.SetValue("Arrow","%SystemRoot%\cursors\arrow_rl.cur")
$RegCursors.SetValue("Crosshair","%SystemRoot%\cursors\cross_r.cur")
$RegCursors.SetValue("Hand","")
$RegCursors.SetValue("Help","%SystemRoot%\cursors\help_r.cur")
$RegCursors.SetValue("IBeam","%SystemRoot%\cursors\beam_r.cur")
$RegCursors.SetValue("No","%SystemRoot%\cursors\no_r.cur")
$RegCursors.SetValue("NWPen","%SystemRoot%\cursors\pen_r.cur")
$RegCursors.SetValue("SizeAll","%SystemRoot%\cursors\move_r.cur")
$RegCursors.SetValue("SizeNESW","%SystemRoot%\cursors\size1_r.cur")
$RegCursors.SetValue("SizeNS","%SystemRoot%\cursors\size4_r.cur")
$RegCursors.SetValue("SizeNWSE","%SystemRoot%\cursors\size2_r.cur")
$RegCursors.SetValue("SizeWE","%SystemRoot%\cursors\size3_r.cur")
$RegCursors.SetValue("UpArrow","%SystemRoot%\cursors\up_r.cur")
$RegCursors.SetValue("Wait","%SystemRoot%\cursors\busy_r.cur")
$RegCursors.Close()
$RegConnect.Close()
function Update-UserPreferencesMask {
$Signature = #"
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, uint pvParam, uint fWinIni);
const int SPI_SETCURSORS = 0x0057;
const int SPIF_UPDATEINIFILE = 0x01;
const int SPIF_SENDCHANGE = 0x02;
public static void UpdateUserPreferencesMask() {
SystemParametersInfo(SPI_SETCURSORS, 0, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
"#
Add-Type -MemberDefinition $Signature -Name UserPreferencesMaskSPI -Namespace User32
[User32.UserPreferencesMaskSPI]::UpdateUserPreferencesMask()
}
Update-UserPreferencesMask
But unfortunately, the cursor size update doesn't work in this way.
A workaround is using arrow_rl.cur (large image) instead of arrow_r.cur.
Refer to Use PowerShell to Change the Mouse Pointer Scheme, Programmatically change custom mouse cursor in windows.

Capturing laptop lid closing event in windows with Powershell

I found some good C# script in the help question "Capturing laptop lid closing event in windows?" (a previous Stack Overflow posted question). Unfortunately I am having trouble converting it to Powershell. My Powershell weaknesses preliminary reside in using Microsoft .NET Framework classes in a Windows PowerShell session, in conjunction with the Add-Type Cmdlet. This is because I do not have any formal script help resources in this area. The Stack Overflow script I was trying to convert to Powershell is listed below.
regards,
Mark
int ServiceMain(int argc, char** argv)
{
serviceStatusHandle = RegisterServiceCtrlHandlerExA(serviceName, (LPHANDLER_FUNCTION_EX) ServiceControlHandler, 0);
//...
lidcloseRegHandle = RegisterPowerSettingNotification(serviceStatusHandle, &GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_SERVICE_HANDLE);
//...
}
/**
* Event handler for windows service.
*/
void WINAPI ServiceControlHandler(DWORD controlCode, DWORD evtype, PVOID evdata, PVOID Context)
{
switch (controlCode)
{
//...
case SERVICE_CONTROL_POWEREVENT:
WriteToLog("Service Control: SERVICE_CONTROL_POWEREVENT builds and fwd the msg");
msg.control = SERVICE_CONTROL_POWEREVENT;
msg.event_type = (int) evtype;
msg.event_data = evdata;
//...
}
}

ExtractAssociatedIcon gives exception in network share using powershell

I am not able to load Icon when accessing from a network share drive in powershell
$IconPath = $pwd.Path + "\Icons\InstallIcon-F.ico"
$HFForm.icon = [System.Drawing.Icon]::ExtractAssociatedIcon($IconPath)
I am getting this error:
Exception calling "ExtractAssociatedIcon" with "1" argument(s): "The given path's format is not supported."
$HFForm.icon = [System.Drawing.Icon]::ExtractAssociatedIcon <<<< ($IconPath)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
My testing shows the same as PeterK's. If I use a drive letter its fine, but an unmapped network share is not.
I was able to make it work by mapping the network share to a drive letter. So:
$something = [system.drawing.icon]::extractassociatedicon("c:\windows\system32\notepad.exe")
caused no errors. Neither did:
$something = [system.drawing.icon]::extractassociatedicon(($test.fullname))
with $test.fullname just being a mapped network file path.
It is a good idea to expand out your variables too so we can see what you're actually passing in. Because if I browse to a network file share and expand $pwd.path:
Microsoft.PowerShell.Core\FileSystem::\\user-pc\users
You are almost certainly passing that in. So have a look. I haven't done much with how Powershell formats its display so I'm sure you can find the 'tty' equiv settings, but just in the meantime do this:
$IconPath = $pwd.Path.split('::')[2] + "\Icons\InstallIcon-F.ico"
Convert your icon to a base64 representation of it's binary data, then store it inside the script itself (as text).
Once it's encoded as base64, you can use this command to convert it back into an icon, bypassing the UNC path issue.
$HFForm.icon = [System.Convert]::FromBase64String('
AAABAAkAAAAAAAEAIABbfQEAlgAAAICAAAABACAAKAgBAPF9AQBgYAAAAQAgAKiUAAAZhgIASEgA
#
# There will be hundreds rows depending on your icon's size.
#
AMADAADAAwAA4AcAAPAPAADwDwAA+A8AAPgPAAD4DwAA/B8AAPwfAAA=')
BASE64 snippet.
#Be sure to edit the path to icon.
#
#Hint the result is copied to your clipboard - clip = clip.exe == google it.
$path = "A:\R2-D2-32x32.ico"
[convert]::ToBase64String((get-content $path -encoding byte)) | Clip
#After running the clip command, right click paste between the quotes
$HFForm.icon = [System.Convert]::FromBase64String('')
i have a solution for it.
At first you have to import the SHGetFileInfo Methode and create the structure SHFILEINFO.
$code = #"
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace System
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
public class SHGETFILEINFO
{
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes,ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
}
}
"#
Add-Type -TypeDefinition $code
Creates the structure object.
#Path to the exe.
$Path = \\test.de\tes
[System.SHFILEINFO]$FileinfoStruct = New-Object System.SHFILEINFO
Gets the size of the structure
$Size = [System.Runtime.InteropServices.Marshal]::SizeOf($FileinfoStruct)
Gets fills the structure Variable with the File infos.
[System.SHGETFILEINFO]::SHGetFileInfo($Path,0, [ref]$FileinfoStruct,$Size,0x000000100)
Creates the icon.
$ICON = [System.Drawing.Icon]::FromHandle($FileinfoStruct.hIcon)