PInvokeHelper.psm1
# Copyright: (c) 2019, Jordan Borean (@jborean93) <jborean93@gmail.com> # MIT License (see LICENSE or https://opensource.org/licenses/MIT) Function Set-MarshalAsAttribute { <# .SYNOPSIS Adds the MarshalAs attribute. .DESCRIPTION Add the [MarshalAs()] attribute to a builder object. .PARAMETER Builder The builder object to add the MarshalAs attribute to. .PARAMETER Type The UnmanagedType value to set on the MarshalAs attribute. .PARAMETER SizeConst The SizeConst value to set on the MarshalAs attribute. .PARAMETER ArraySubType The ArraySubType value to set on the MarshalAs attribute. .EXAMPLE Set-MarshalAsAttribute -Builder $method_builder -Type LPWStr -SizeConst 8 #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification="This does make a system change but localised to the passed in builder")] [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [Object] $Builder, [Parameter(Mandatory=$true)] [System.Runtime.InteropServices.UnmanagedType] $Type, [System.Int32] $SizeConst, [System.Runtime.InteropServices.UnmanagedType] $ArraySubType ) $ctor_info = [System.Runtime.InteropServices.MarshalAsAttribute].GetConstructor( [System.Runtime.InteropServices.UnmanagedType] ) $no_params = @{ TypeName = 'System.Reflection.Emit.CustomAttributeBuilder' ArgumentList = [System.Collections.Generic.List`1[Object]]@($ctor_info, $Type) } $field_array = [System.Collections.Generic.List`1[System.Reflection.FieldInfo]]@() $field_values = [System.Collections.Generic.List`1[Object]]@() if ($null -ne $SizeConst) { $field_array.Add([System.Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst')) $field_values.Add($SizeConst) } if ($null -ne $ArraySubType) { $field_array.Add([System.Runtime.InteropServices.MarshalAsAttribute].GetField('ArraySubType')) $field_values.Add($ArraySubType) } $no_params.ArgumentList.Add($field_array.ToArray()) $no_params.ArgumentList.Add($field_values.ToArray()) $attribute_builder = New-Object @no_params $Builder.SetCustomAttribute($attribute_builder) } Function Get-Win32ErrorMessage { <# .SYNOPSIS Gets the Win32 Error message. .DESCRIPTION Gets the Win32 Error message based on error code passed in .PARAMETER ErrorCode The error code to convert. \When calling a PInvoke function this can be retrieved with [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() This should be done inline, e.g. [PInvoke]::Function(); $err_code = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() Failure to do this in line may result in the wrong error code being retrieved. .OUTPUTS System.String. The Win32 error message for the code specified. .EXAMPLE $res = [PInvoke]::CreatePipe( [Ref]$ReadHandle, [Ref]$WriteHandle, 0, 0 ); $err_code = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() if (-not $res) { $msg = Get-Win32ErrorMessage -ErrorCode $err_code throw $msg } #> [OutputType([System.String])] [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.Int32] $ErrorCode ) $exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $ErrorCode ('{0} (Win32 ErrorCode {1} - 0x{1:X8})' -f $exp.Message, $ErrorCode) } Function Import-Enum { <# .SYNOPSIS Defines an enum. .DESCRIPTION Defines an enum in the module builder specified. .PARAMETER ModuleBuilder The ModuleBuilder object to define the enum in. .PARAMETER Name The name of the enum to define. .PARAMETER Type The enum base type. .PARAMETER Values A hashtable of enum key/values to define. .PARAMETER Flags Whether the enum has the [Flags] attribute applied. .EXAMPLE $module_builder = New-DynamicModule -Name PInvokeHelper Defines [Flags] public LogonFlags : uint { WithoutProfile = 0, WithProfile = 1, NetCredentialsOnly = 2 } $enum = @{ ModuleBuilder = $module_builder Name = 'LogonFlags' Type = ([System.UInt32]) Flags = $true Values = @{ WithoutProfile = 0 WithProfile = 1 NetCredentialsOnly = 2 } } Import-Enum @enum Defines public TokenInformationClass : int { User = 1, Groups = 2, Privileges = 3 } $enum = @{ ModuleBuilder = $module_builder Name = 'TokenInformationClass' Type = ([System.Int32]) Values = @{ User = 1 Groups = 2 Privileges = 3 } } Import-Enum @enum #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.Reflection.Emit.ModuleBuilder] $ModuleBuilder, [Parameter(Mandatory=$true)] [System.String] $Name, [Parameter(Mandatory=$true)] [Type] $Type, [Parameter(Mandatory=$true)] [System.Collections.IDictionary] $Values, [Switch] $Flags ) $enum_builder = $ModuleBuilder.DefineEnum($Name, 'Public', $Type) # Add the Flags attribute if required if ($Flags) { $ctor_info = [System.FlagsAttribute].GetConstructor([Type]::EmptyTypes) $attribute_builder = New-Object -TypeName System.Reflection.Emit.CustomAttributeBuilder -ArgumentList @( $ctor_info, @() ) $enum_builder.SetCustomAttribute($attribute_builder) } foreach ($kvp in $Values.GetEnumerator()) { $value = $kvp.Value -as $Type $enum_builder.DefineLiteral($kvp.Key, $value) > $null } $enum_builder.CreateType() > $null } Function Import-PInvokeMethod { <# .SYNOPSIS Defines a Win32 function as a PInvoke method. .DESCRIPTION Defines a Win32 function as a PInvoke method and control some of the marshaling techniques used for that function. .PARAMETER TypeBuilder A TypeBuilder that represents the class to define the static methods in. .PARAMETER DllName The name of the Dll that contains the function to import. .PARAMETER Name The name of the Win32 function to import. .PARAMETER ReturnType The type that is returned by the function. Can also be a Hashtable with the following keys; Type: The type of the return value of the function. MarshalAs: An optional hash that contains the parameter for Set-MarshalAsAttribute. Type: Sets the [MarshalAs(UnmanagedType.)] value for the parameter. SizeConst: Optional SizeConst value for MarshalAs. ArraySubType: Optional ArraySubType value for MarshalAs. .PARAMETER ParameterTypes A list of types of the functions parameters. Each parameter is represented by a [Type] value for a simple parameter type but can also be a hashtable with the following keys; Type: Must be set to the type of the parameter Ref: Whether it is a [Ref] type, e.g. is ref or has an out parameter in .NET. MarshalAs: An optional hash that contains the parameter for Set-MarshalAsAttribute. Type: Sets the [MarshalAs(UnmanagedType.)] value for the parameter. SizeConst: Optional SizeConst value for MarshalAs. ArraySubType: Optional ArraySubType value for MarshalAs. .PARAMETER SetLastError Sets the 'SetLastError=true' field on the DllImport attribute of the function. .PARAMETER CharSet Sets the 'CharSet' field on the DllImport attribute of the function. .PARAMETER PassThru Output the MethodBuilder of the defined function. .OUTPUTS None, System.Reflection.Emit.MethodBuilder This cmdlet generates a System.Reflection.Emit.MethodInfo if you specify -PassThru, Otherwise this cmdlet does not return any output. .EXAMPLE $module_builder = New-DynamicModule -Name PInvokeHelper $type_builder = $module_buidler.DefineType('PInvokeHelper.NativeMethods', 'Public, Class') $function_definition = @{ DllName = 'Kernel32.dll' Name = 'CreateProcessW' ReturnType = ([System.Boolean]) ParameterTypes = @( @{ Type = ([System.String]) MarshalAs = @{ Type = [System.Runtime.InteropServices.UnmanagedType]::LPWStr } }, [System.Text.StringBuilder], [System.IntPtr], [System.IntPtr], [System.Boolean], [PSLimitedProcess.ProcessCreationFlags], [System.IntPtr], @{ Type = ([System.String]) MarshalAs = @{ Type = [System.Runtime.InteropServices.UnmanagedType]::LPWStr } }, @{ Ref = $true; Type = [PSLimitedProcess.STARTUPINFOEX] }, @{ Ref = $true; Type = [PSLimitedProcess.PROCESS_INFORMATION] } ) SetLastError = $true } Import-PInvokeMethod -TypeBuilder $type_builder @function_definition # Call once all functions have been defined in the type/class $type_builder.CreateType() > $null #> [OutputType([System.Reflection.Emit.MethodBuilder])] [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.Reflection.Emit.TypeBuilder] $TypeBuilder, [Parameter(Mandatory=$true)] [System.String] $DllName, [Parameter(Mandatory=$true)] [System.String] $Name, [Parameter(Mandatory=$true)] [Object] $ReturnType, [Object[]] $ParameterTypes = [Object[]]@(), [Switch] $SetLastError, [Runtime.InteropServices.CharSet] $CharSet = [System.Runtime.InteropServices.CharSet]::Auto, [Switch]$PassThru ) # ParameterTypes is either an array of Type or Hashtable where the Hashtable can contain # Ref = Whether the parameter is an in/out parameter # Type = The actual type of the parameter, this must be set when using a hashtable # MarshalAs = A hashtable that defines the custom MarshalAs attributes. See Set-MarshalAsAttribute $parameter_types = [System.Collections.Generic.List`1[Type]]@() $parameter_marshal_as = @{} for ($i = 0; $i -lt $ParameterTypes.Length; $i++) { $parameter = $ParameterTypes[$i] if ($parameter -is [System.Collections.IDictionary]) { if ($parameter.ContainsKey('Ref') -and $parameter.Ref) { $parameter_types.Add(($parameter.Type -as [Type]).MakeByRefType()) } else { $parameter_types.Add(($parameter.Type -as [Type])) } if ($parameter.ContainsKey('MarshalAs')) { $parameter_idx = $i + 1 $parameter_marshal_as.$parameter_idx = $parameter.MarshalAs } } else { $parameter_types.Add(($parameter -as [Type])) } } # Cast the defined return type as a type. Check if the parameter is a Hashtable that defines the MarshalAs attr $marshal_as = $null if ($ReturnType -is [System.Collections.IDictionary]) { $return_type = $ReturnType.Type -as [Type] if ($ReturnType.ContainsKey('MarshalAs')) { $marshal_as = $ReturnType.MarshalAs } } else { $return_type = $ReturnType -as [Type] } # Next, the method is created where we specify the name, parameters and # return type that is expected $method_builder = $TypeBuilder.DefineMethod( $Name, [System.Reflection.MethodAttributes]'Public, Static', $return_type, $parameter_types ) # Set the retval MarshalAs attribute if set if ($null -ne $marshal_as) { $parameter_builder = $method_builder.DefineParameter( 0, [System.Reflection.ParameterAttributes]::Retval, $null ) Set-MarshalAsAttribute -Builder $parameter_builder @marshal_as } # Set the parameter MarshalAs attribute if set foreach ($marshal_info in $parameter_marshal_as.GetEnumerator()) { $param_idx = $marshal_info.Key $marshal_as = $marshal_info.Value $parameter_builder = $method_builder.DefineParameter( $param_idx, [System.Reflection.ParameterAttributes]::None, $null ) Set-MarshalAsAttribute -Builder $parameter_builder @marshal_as } # Set the DllImport() attributes; SetLastError and CharSet $dll_ctor = [System.Runtime.InteropServices.DllImportAttribute].GetConstructor([System.String]) $method_fields = [System.Reflection.FieldInfo[]]@( [System.Runtime.InteropServices.DllImportAttribute].GetField('SetLastError'), [System.Runtime.InteropServices.DllImportAttribute].GetField('CharSet') ) $method_fields_values = [Object[]]@($SetLastError.IsPresent, $CharSet) $dll_import_attr = New-Object -TypeName System.Reflection.Emit.CustomAttributeBuilder -ArgumentList @( $dll_ctor, $DllName, $method_fields, $method_fields_values ) $method_builder.SetCustomAttribute($dll_import_attr) if ($PassThru) { $method_builder } } Function Import-Struct { <# .SYNOPSIS Defines a struct. .DESCRIPTION Defines a struct in the ModuleBuilder specified. .PARAMETER ModuleBuilder The ModuleBuilder to define the struct in. .PARAMETER Name The name of the struct to define. .PARAMETER Fields A list of hashes that define the fields of the struct. Each value in the fields list should be a hash with the following keys; Name: The name of the field. Type: The type of the field. MarshalAs: Optional hash that defines the MarshalAs attribute based on the Set-MarshalAsAttribute parameters. Type: Sets the [MarshalAs(UnmanagedType.)] value for the parameter. SizeConst: Optional SizeConst value for MarshalAs. ArraySubType: Optional ArraySubType value for MarshalAs. .PARAMETER PackingSize Set the Pack value for the StructLayout attribute. .EXAMPLE $module_builder = New-DynamicModule -Name PInvokeHelper $struct_definition = @{ Name = 'TOKEN_PRIVILEGES' Fields = @( @{ Name = 'PrivilegeCount' Type = ([System.UInt32]) }, @{ Name = 'Privileges' Type = 'LUID_AND_ATTRIBUTES[]' MarshalAs = @{ Type = [System.Runtime.InteropServices.UnmanagedType]::ByValArray SizeConst = 1 } } ) } Import-Struct -ModuleBuilder $module_builder @struct_definition #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.Reflection.Emit.ModuleBuilder] $ModuleBuilder, [Parameter(Mandatory=$true)] [System.String] $Name, [Parameter(Mandatory=$true)] [Hashtable[]] $Fields, [System.Reflection.Emit.PackingSize] $PackingSize = [System.Reflection.Emit.PackingSize]::Unspecified ) $struct_builder = $ModuleBuilder.DefineType( $Name, 'Class, Public, Sealed, BeforeFieldInit, AutoLayout, SequentialLayout', [System.ValueType], $PackingSize ) foreach ($field in $Fields) { # Make sure we cast the Type key for the field as an actual [Type]. This allows the Type definition to be # defined as a string and only casted when it's actually needed. $field_type = $field.Type -as [Type] # Add a custom MarshalAs attribute if required. This should contain the Type and the optional SizeConst key. if ($field.ContainsKey('MarshalAs')) { $field_builder = $struct_builder.DefineField($field.Name, $field_type, 'Public, HasFieldMarshal') $marshal_as = $field.MarshalAs Set-MarshalAsAttribute -Builder $field_builder @marshal_as } else { $struct_builder.DefineField($field.Name, $field_type, 'Public') > $null } } $struct_builder.CreateType() > $null } Function New-DynamicModule { <# .SYNOPSIS Creates a dynamic module. .DESCRIPTION Creates a dynamic module that can then be used by other functions in PInvokeHelper when defining other structs, enums, and methods. .PARAMETER Name The unique assembly and module name to define the builder in. The assembly with be "$($Name)Assembly" and the module will be "$($Name)Module". .OUTPUTS Creates a dynamic module that can be used to define further types like classes, enums, structs and so forth. .EXAMPLE New-DynamicModule -Name PInvokeHelper #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification="This does make a system change")] [OutputType([System.Reflection.Emit.ModuleBuilder])] [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.String] $Name ) $assembly_name = "$($Name)Assembly" $module_name = "$($Name)Module" $domain = [System.AppDomain]::CurrentDomain # Create the dynamic assembly that contains our new module. We set the access to Run so that we don't save the # defined types to disk. $dynamic_assembly = New-Object -TypeName System.Reflection.AssemblyName -ArgumentList $assembly_name $assembly_builder = $domain.DefineDynamicAssembly( $dynamic_assembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run ) $dynamic_module = $assembly_builder.DefineDynamicModule($module_name, $false) return $dynamic_module } $public_functions = @('Get-Win32ErrorMessage', 'Import-Enum', 'Import-PInvokeMethod', 'Import-Struct', 'New-DynamicModule') <# Defines the PInvokeHelper.SafeNativeHandle class that guarantees a native handle to be closed. The C# type looks like public class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid { public SafeNativeHandle() : base(true) { } public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; } public static implicit operator IntPtr(SafeNativeHandle h) { return h.DangerousGetHandle(); } [DllImport("Kernel32.dll", SetLastError = true)] private static extern bool CloseHandle( IntPtr pObject); [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return CloseHandle(handle); } } #> $module_builder = New-DynamicModule -Name PInvokeHelper $type_builder = $module_builder.DefineType( 'PInvokeHelper.SafeNativeHandle', [System.Reflection.TypeAttributes]'AutoLayout, AnsiClass, Class, Public, BeforeFieldInit', [Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid] ) # Get the base constructor and handle field objects for use in the IL method definition $base_type = [Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid] $base_ctor = $base_type.GetConstructor( [System.Reflection.BindingFlags]'Instance, NonPublic', $null, [Type[]]@(,[System.Boolean]), $null ) $base_handle_field = $base_type.GetField('handle', [System.Reflection.BindingFlags]'Instance, NonPublic') # Define the constructor blocks $ctor1 = $type_builder.DefineConstructor( [System.Reflection.MethodAttributes]'PrivateScope, Public, HideBySig, SpecialName, RTSpecialName', [System.Reflection.CallingConventions]'Standard, HasThis', @() ) $ctor1_il = $ctor1.GetILGenerator() $ctor1_il.Emit([System.Reflection.Emit.OpCodes]::Ldarg_0) $ctor1_il.Emit([System.Reflection.Emit.OpCodes]::Ldc_I4, 1) # Sets $true onto stack $ctor1_il.Emit([System.Reflection.Emit.OpCodes]::Call, $base_ctor) # calls base(true) $ctor1_il.Emit([System.Reflection.Emit.OpCodes]::Ret) $ctor2 = $type_builder.DefineConstructor( [System.Reflection.MethodAttributes]'PrivateScope, Public, HideBySig, SpecialName, RTSpecialName', [System.Reflection.CallingConventions]'Standard, HasThis', [Type[]]@(,[System.IntPtr]) ) $ctor2_il = $ctor2.GetILGenerator() $ctor2_il.Emit([System.Reflection.Emit.OpCodes]::Ldarg_0) $ctor2_il.Emit([System.Reflection.Emit.OpCodes]::Ldc_I4, 1) $ctor2_il.Emit([System.Reflection.Emit.OpCodes]::Call, $base_ctor) $ctor2_il.Emit([System.Reflection.Emit.OpCodes]::Ldarg_0) $ctor2_il.Emit([System.Reflection.Emit.OpCodes]::Ldarg_1) # Loads the IntPtr arg passed into the constructor $ctor2_il.Emit([System.Reflection.Emit.OpCodes]::Stfld, $base_handle_field) # Sets the handle field with param1 $ctor2_il.Emit([System.Reflection.Emit.OpCodes]::Ret) # Define the CloseHandle method $close_handle_builder = $type_builder.DefineMethod( 'CloseHandle', [System.Reflection.MethodAttributes]'Private, Static', [System.Boolean], [Type[]]@([System.IntPtr]) ) $dll_import_attr = New-Object -TypeName System.Reflection.Emit.CustomAttributeBuilder -ArgumentList @( [System.Runtime.InteropServices.DllImportAttribute].GetConstructor([System.String]), 'Kernel32.dll', [System.Reflection.FieldInfo[]]@([System.Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')), [Object[]]@($true) ) $close_handle_builder.SetCustomAttribute($dll_import_attr) # Define the ReleaseHandle() method $method_builder = $type_builder.DefineMethod( 'ReleaseHandle', [System.Reflection.MethodAttributes]'PrivateScope, Family, Virtual, HideBySig', [System.Boolean], @() ) $method_il = $method_builder.GetILGenerator() $method_il.Emit([System.Reflection.Emit.OpCodes]::Ldarg_0) $method_il.Emit([System.Reflection.Emit.OpCodes]::Ldfld, $base_handle_field) # Load the value of the handle field $method_il.Emit([System.Reflection.Emit.OpCodes]::Call, $close_handle_builder) # Call the CloseHandle method $method_il.Emit([System.Reflection.Emit.OpCodes]::Ret) # Set [ReliabilityContract(Consistency.WillNotCorrupState, Cer.MayFail)] on the ReleaseHandle() method $reliability_attr = [System.Runtime.ConstrainedExecution.ReliabilityContractAttribute].GetConstructor( [Type[]]@([System.Runtime.ConstrainedExecution.Consistency], [System.Runtime.ConstrainedExecution.Cer]) ) $ca = New-Object -TypeName System.Reflection.Emit.CustomAttributeBuilder -ArgumentList @( $reliability_attr, @( [System.Runtime.ConstrainedExecution.Consistency]::WillNotCorruptState, [System.Runtime.ConstrainedExecution.Cer]::MayFail ) ) $method_builder.SetCustomAttribute($ca) # Define implicit operator to IntPtr. This allows the SafeNativeHandle to be used in place of IntPtr without any # explicit casts. Technically it is dangerous but we are using this type to make sure the handle is disposed. $dangerous_get_handle = $base_type.GetMethod('DangerousGetHandle') $impl_method = $type_builder.DefineMethod( 'op_Implicit', [System.Reflection.MethodAttributes]'Public, HideBySig, SpecialName, Static', [System.IntPtr], @(,$type_builder) ) $impl_il = $impl_method.GetILGenerator() $impl_il.Emit([System.Reflection.Emit.OpCodes]::Ldarg_0) $impl_il.Emit([System.Reflection.Emit.OpCodes]::Callvirt, $dangerous_get_handle) $impl_il.Emit([System.Reflection.Emit.OpCodes]::Ret) $type_builder.CreateType() > $null Export-ModuleMember -Function $public_functions |