Com & Un-Managed Warper for PS1.

Discussion in 'Scripting' started by Dark Vador, Jul 7, 2025.

  1. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    #1 Dark Vador, Jul 7, 2025
    Last edited: Aug 3, 2025 at 16:35
    Managed Com / win32Api Warper for PS1
    it's not the usual com warper you would think of -
    some more explained info.

    Code:
    * first, it accept, CLSID only or IID too
      better pass IID, so, it could check, it 7/3 base function
      else, it might fail, you be warned
    * second, Create & release now become idiot proof
    * use delagate C# function dynamcly Create'd, instead build interface/struct's
      user just have to write C# matched paramters, and matched output
    * you don't have calculate position, +3+7, it will do the doding.
      basic com 3+, Idispatch is 3+4+, it will auto detect disptach
      and auto find location of function
    * its totaly idiot proof, you manage Objects, and move object's
      to initilzie, invoke, release, the logic done automaticly,
      and also get you forget mistake, i do suggest Try, Catch, Finish - release.
    * you have to pre make parameter's and validate, CLSID,IID, RETURN. PARAMS
      and call delegate with right parameter.
    
    what you really need, is basic skill, of how function should look
    you can also advise with AI, they good understand C/C+ code style


    delegate will be be add Type of C# code, Out, Ref, etc
    but powershell doe's not have Out, it always [REF],
    so, take it in account, when call delegate later

    Demo Code for example.
    { from netlistmgr.h }
    Code:
        MIDL_INTERFACE("DCB00000-570F-4A9B-8D69-199FDBA5723B")
        INetworkListManager : public IDispatch
        {
        public:
            virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetNetworks(
                /* [in] */ NLM_ENUM_NETWORK Flags,
                /* [retval][out] */ __RPC__deref_out_opt IEnumNetworks **ppEnumNetwork) = 0;
     
            virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetNetwork(
                /* [in] */ GUID gdNetworkId,
                /* [retval][out] */ __RPC__deref_out_opt INetwork **ppNetwork) = 0;
     
            virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetNetworkConnections(
                /* [retval][out] */ __RPC__deref_out_opt IEnumNetworkConnections **ppEnum) = 0;
     
            virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetNetworkConnection(
                /* [in] */ GUID gdNetworkConnectionId,
                /* [retval][out] */ __RPC__deref_out_opt INetworkConnection **ppNetworkConnection) = 0;
     
            virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_IsConnectedToInternet( 
                /* [retval][out] */ __RPC__out VARIANT_BOOL *pbIsConnected) = 0;
     
            virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_IsConnected(
                /* [retval][out] */ __RPC__out VARIANT_BOOL *pbIsConnected) = 0;
     
            virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetConnectivity(
                /* [retval][out] */ __RPC__out NLM_CONNECTIVITY *pConnectivity) = 0;
     
        };
    
    class DECLSPEC_UUID("DCB00C01-570F-4A9B-8D69-199FDBA5723B")
    NetworkListManager;
    #endif
    #endif /* __NETWORKLIST_LIBRARY_DEFINED__ */
    
    so first --> basic parse
    Code:
    interface = DCB00000-570F-4A9B-8D69-199FDBA5723B
    class = DCB00C01-570F-4A9B-8D69-199FDBA5723B
    Position = 5
    paramaters = VARIANT_BOOL *pbIsConnected
    return = HRESULT
    Name = IsConnectedToInternet
    
    SO, what can do with this ?
    here the magic happen
    Code:
    # netlistmgr.h
    # https://raw.githubusercontent.com/nihon-tc/Rtest/refs/heads/master/header/Microsoft%20SDKs/Windows/v7.0A/Include/netlistmgr.h
    
    # get_IsConnectedToInternet
    # https://learn.microsoft.com/en-us/windows/win32/api/netlistmgr/nf-netlistmgr-inetworklistmanager-get_isconnectedtointernet
    
    $interfaceSpec = [PSCustomObject]@{
        Index  = (5)
        Return = "int"
        Name   = "IsConnectedToInternet"
        Params = "out short pbIsConnected"
        IID    = "DCB00000-570F-4A9B-8D69-199FDBA5723B"
        CLSID  = "DCB00C01-570F-4A9B-8D69-199FDBA5723B"
    }
    
    $comObj = Initialize-ComObject -InterfaceSpec $interfaceSpec
    [int16]$rawIsConnected = 0
    
    $hresult = $comObj | Invoke-ComObject -Params ([ref]$rawIsConnected)
    if ($hresult -eq 0) {
        $isConnectedBool = ($rawIsConnected -ne 0)
        Write-Host "Connected to Internet: $isConnectedBool"
    } else {
        Write-Warning "Failed to get internet connection status. HRESULT: $hresult"
    }
    $comObj | Release-ComObject
    
    -------------------------
    Added support for Unmanaged Api call using delegate.

    Code:
    Clear-host
    write-host
    
    $Global:ntdll = Init-NTDLL
    $Global:kernel32 = Init-KERNEL32
    $Global:PebPtr = $Global:ntdll::RtlGetCurrentPeb()
    $global:LoadedModules = Get-LoadedModules -SortType Memory |
        Select-Object BaseAddress, ModuleName, LoadAsData
    
    # Com Example
    Use-ComInterface `
        -CLSID "17CCA47D-DAE5-4E4A-AC42-CC54E28F334A" `
        -IID "f2dcb80d-0670-44bc-9002-cd18688730af" `
        -Index 3 `
        -Name "ShowProductKeyUI" `
        -Return "void"
    
    Invoke-UnmanagedMethod `
        -Dll "kernel32.dll" `
        -Function "Beep" `
        -Return "bool" `
        -Params "uint dwFreq, uint dwDuration" `
        -Values @(750, 300)  # 750 Hz beep for 300 ms
    
    -------------------------
    expend it to able Load memoey module's, Enum memory module's
    & Other good stuff, Like Create Memory, Or Ref, free Memoey from all type's
    Etc, etc etc etc
    -------------------------
    Warper source Code

    https://forums.mydigitallife.net/threads/com-warper-for-ps1.89437/#post-1883895
     

    Attached Files:

    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  2. Carlos Detweiller

    Carlos Detweiller Emperor of Ice-Cream
    Staff Member

    Dec 21, 2012
    7,268
    8,910
    240
    Do you mean "Wrapper" maybe (like conduit)?
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  3. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    #3 Dark Vador, Jul 7, 2025
    Last edited: Jul 28, 2025 at 22:11
    (OP)
    Basic code, only !
    use attached files from first post
    For updated latest revision

    Code:
    # work - job Here.
    $Global:ntdll = Init-NTDLL
    $Global:kernel32 = Init-KERNEL32
    $Global:PebPtr = $Global:ntdll::RtlGetCurrentPeb()
    $global:LoadedModules = Get-LoadedModules -SortType Memory |
        Select-Object BaseAddress, ModuleName, LoadAsData
    
    # Com Example
    Clear-host
    Write-Host
    
    # ZwQuerySystemInformation
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677
    
    # SYSTEM_PROCESS_INFORMATION structure
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/process.htm
    
    # ZwQuerySystemInformation
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677
    
    # SYSTEM_BASIC_INFORMATION structure
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi/system_basic_information.htm
    
    # Step 1: Get required buffer size
    $ReturnLength = 0
    $dllResult = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(0, [IntPtr]::Zero, 0, [ref]$ReturnLength)
    
    # Allocate buffer (add some extra room just in case)
    $infoBuffer = New-IntPtr -Size $ReturnLength
    
    # Step 2: Actual call with allocated buffer
    $result = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(0, $infoBuffer, $ReturnLength, [ref]$ReturnLength)
    
    if ($result -ne 0) {
        Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)"
        Parse-ErrorMessage -MessageId $result
        New-IntPtr -hHandle $infoBuffer -Release
        return
    }
    
    # Parse values from the structure
    $sysBasicInfo = [PSCustomObject]@{
        PageSize                     = [Marshal]::ReadInt32($infoBuffer,  0x08)
        NumberOfPhysicalPages        = [Marshal]::ReadInt32($infoBuffer,  0x0C)
        LowestPhysicalPageNumber     = [Marshal]::ReadInt32($infoBuffer,  0x10)
        HighestPhysicalPageNumber    = [Marshal]::ReadInt32($infoBuffer,  0x14)
        AllocationGranularity        = [Marshal]::ReadInt32($infoBuffer,  0x18)
        MinimumUserModeAddress       = [Marshal]::ReadIntPtr($infoBuffer, 0x20)
        MaximumUserModeAddress       = [Marshal]::ReadIntPtr($infoBuffer, 0x28)
        ActiveProcessorsAffinityMask = [Marshal]::ReadIntPtr($infoBuffer, 0x30)
        NumberOfProcessors           = [Marshal]::ReadByte($infoBuffer,   0x38)
    }
    
    # Step 1: Get required buffer size
    $ReturnLength = 0
    $dllResult = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(5, [IntPtr]::Zero, 0, [ref]$ReturnLength)
    
    # Allocate buffer (add some extra room just in case)
    $ReturnLength += 200
    $procBuffer = New-IntPtr -Size $ReturnLength
    
    # Step 2: Actual call with allocated buffer
    $result = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(5, $procBuffer, $ReturnLength, [ref]$ReturnLength)
    
    if ($result -ne 0) {
        Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)"
        Parse-ErrorMessage -MessageId $result
        New-IntPtr -hHandle $procBuffer -Release
        return
    }
    
    $offset = 0
    $processList = @()
    
    while ($true) {
        try {
            $entryPtr = [IntPtr]::Add($procBuffer, $offset)
            $nextOffset = [Marshal]::ReadInt32($entryPtr, 0x00)
    
            $namePtr = [Marshal]::ReadIntPtr($entryPtr, 0x38 + [IntPtr]::Size)
            $processName = if ($namePtr -ne [IntPtr]::Zero) {
                [Marshal]::PtrToStringUni($namePtr)
            } else {
                "[System]"
            }
    
            $procObj = [PSCustomObject]@{
                ProcessId       = [Marshal]::ReadIntPtr($entryPtr, 0x50)
                ProcessName     = $processName
                NumberOfThreads = [Marshal]::ReadInt32($entryPtr, 0x04)
            }
    
            $processList += $procObj
    
            if ($nextOffset -eq 0) { break }
            $offset += $nextOffset
        } catch {
            Write-Host "Parsing error at offset $offset. Stopping."
            break
        }
    }
    
    New-IntPtr -hHandle $infoBuffer -Release
    New-IntPtr -hHandle $procBuffer -Release
    
    $sysBasicInfo | Format-List
    $processList | Sort-Object ProcessName | Format-Table ProcessId, ProcessName, NumberOfThreads -AutoSize
    
    [intPtr]$ppEnumNetwork = [intPtr]::Zero
    Use-ComInterface `
        -CLSID "DCB00C01-570F-4A9B-8D69-199FDBA5723B" `
        -IID "DCB00000-570F-4A9B-8D69-199FDBA5723B" `
        -Index 1 `
        -Name "GetNetwork" `
        -Return "uint" `
        -Params 'system.UINT32 Flags, out INTPTR ppEnumNetwork' `
        -Values @(1, [ref]$ppEnumNetwork)
    
    if ($ppEnumNetwork -ne [IntPtr]::Zero) {
        $networkList = $ppEnumNetwork | Receive-ComObject
        foreach ($network in $networkList) {
            "Name: $($network.GetName()), IsConnected: $($network.IsConnected())"
        }
        $networkList | Release-ComObject
    }
    
    
    PS1 Libary
    Code:
    
    using namespace System.Reflection
    using namespace System.Reflection.Emit
    using namespace System.Runtime.InteropServices
    
    # WIN32 API Parts
    function New-IntPtr {
        param(
            [Parameter(Mandatory=$false)]
            [int]$Size,
    
            [Parameter(Mandatory=$false)]
            [int]$InitialValue = 0,
    
            [Parameter(Mandatory=$false)]
            [IntPtr]$hHandle,
    
            [switch]$WriteSizeAtZero,
            [switch]$Release
        )
    
        if ($hHandle -or $Release) {
            if ($Size) {
                throw "Size option can't go with *hHandle or *Release"
            }
            if (-not $hHandle -or -not $Release) {
                throw "hHandle & Release, Must use both parametes"
            }
        }
    
         if ($Release) {
            if ($hHandle -and $hHandle -ne [IntPtr]::Zero) {
                [Marshal]::FreeHGlobal($hHandle)
            }
            return
        }
    
        # Allocate unmanaged memory
        $ptr = [Marshal]::AllocHGlobal($Size)
    
        # Zero memory
        $Global:ntdll::RtlZeroMemory($ptr, [UIntPtr]::new($Size))
    
        # Write size at offset 0 if requested
        if ($WriteSizeAtZero) {
            [Marshal]::WriteInt32($ptr, 0, $Size)
        }
        # Otherwise, if size == 4 and initial value != 0, write initial value
        elseif ($Size -eq 4 -and $InitialValue -ne 0) {
            [Marshal]::WriteInt32($ptr, 0, $InitialValue)
        }
    
        return $ptr
    }
    Function Init-NTDLL {
        $Method = [PSCustomObject]@{
            attributes         = [MethodAttributes]::Public -bor [MethodAttributes]::Static -bor [MethodAttributes]::PinvokeImpl
            CallingConventions = [CallingConventions]::Standard
            nativeCallConv     = [CallingConvention]::Winapi
            nativeCharSet      = [CharSet]::Unicode
            ImplAttributes     = [MethodImplAttributes]::PreserveSig
            TypeAttributes     = [TypeAttributes]::Public -bor [TypeAttributes]::Abstract -bor [TypeAttributes]::Sealed
        }
    
        # Define dynamic type and methods before main try-finally
        $asmName = New-Object AssemblyName "NativeOSVersionAssembly"
        $asm     = [AppDomain]::CurrentDomain.DefineDynamicAssembly($asmName, [AssemblyBuilderAccess]::Run)
        $mod     = $asm.DefineDynamicModule("NativeOSVersionModule")
        $tb      = $mod.DefineType("NativeMethods", $Method.TypeAttributes)
    
        $NtdllFunctions = @(
            @{ Name = "RtlGetVersion";             Dll = "ntdll.dll"; ReturnType = [UInt32];     Parameters = [IntPtr] },
            @{ Name = "RtlGetCurrentPeb";          Dll = "ntdll.dll"; ReturnType = [IntPtr];  Parameters = @() },
            @{ Name = "RtlGetProductInfo";         Dll = "ntdll.dll"; ReturnType = [bool];    Parameters = [Type[]]@([UInt32], [UInt32], [UInt32], [UInt32], [IntPtr]) },
            @{ Name = "RtlGetNtVersionNumbers";    Dll = "ntdll.dll"; ReturnType = [void];    Parameters = ([Uint32].MakeByRefType(), [Uint32].MakeByRefType(), [Uint32].MakeByRefType()) },
            @{ Name = "RtlZeroMemory";             Dll = "ntdll.dll"; ReturnType = [void];    Parameters = [Type[]]@([IntPtr], [UIntPtr]) },
            @{ Name = "RtlGetProcessHeaps";        Dll = "ntdll.dll"; ReturnType = [UInt32];  Parameters = [Type[]]@([UInt32], [IntPtr]) },
            @{ Name = "NtQueryInformationProcess"; Dll = "ntdll.dll"; ReturnType = [UInt32];  Parameters = [Type[]]@([IntPtr], [UInt32], [IntPtr], [UInt32], [IntPtr]) },
            @{ Name = "NtQuerySystemInformation";  Dll = "ntdll.dll"; ReturnType = [UInt32];  Parameters = [Type[]]@([UInt32], [IntPtr], [UInt32], [IntPtr]) },
            @{ Name = "ZwQueryLicenseValue";       Dll = "ntdll.dll"; ReturnType = [UInt32];  Parameters = [Type[]]@([IntPtr], [UInt32].MakeByRefType(), [IntPtr], [UInt32], [UInt32].MakeByRefType()) },
            @{ Name = "RtlCreateUnicodeString";    Dll = "ntdll.dll"; ReturnType = [uint64];  Parameters = [Type[]]@([IntPtr], [string]) },
            @{ Name = "RtlFreeUnicodeString";      Dll = "ntdll.dll"; ReturnType = [UInt32];  Parameters = [Type[]]@([IntPtr]) },
            @{ Name = "RtlFindMessage";            Dll = "ntdll.dll"; ReturnType = [UInt32];  Parameters = [Type[]]@(
                [IntPtr],                 # DllHandle
                [Uint32],                 # MessageTableId
                [Uint32],                 # MessageLanguageId
                [Uint32],                 # MessageId // ULONG
                [IntPtr].MakeByRefType()  # ref IntPtr for output MESSAGE_RESOURCE_ENTRY*
            ) },
            @{ Name = "RtlNtStatusToDosError";     Dll = "ntdll.dll";     ReturnType = [UInt32];   Parameters = [Type[]]@([Int32]) },
            @{ Name = "LsaNtStatusToWinError";     Dll = "advapi32.dll";  ReturnType = [UInt32] ;  Parameters = [Type[]]@([UInt32]) },
           
            <#
                [In]String\Flags, [In][REF]Flags, [In][REF]UNICODE_STRING, [Out]Handle
                void LdrLoadDll(ulonglong param_1,uint *param_2,uint *param_3,undefined8 *param_4)
    
                https://rextester.com/KCUV42565
                RtlInitUnicodeStringStruct (&unicodestring, L"USER32.dll");
                LdrLoadDllStruct (NULL, 0, &unicodestring, &hModule);
    
                https://doxygen.reactos.org/d7/d55/ldrapi_8c_source.html
                NTSTATUS
                NTAPI
                DECLSPEC_HOTPATCH
                LdrLoadDll(
                    _In_opt_ PWSTR SearchPath,
                    _In_opt_ PULONG DllCharacteristics,
                    _In_ PUNICODE_STRING DllName,
                    _Out_ PVOID *BaseAddress)
                {
            #>
            @{ Name = "LdrLoadDll";    Dll = "ntdll.dll";     ReturnType = [UInt32];      Parameters = [Type[]]@(
               
                # [IntPtr]::Zero // [STRING] -> NULL -> C Behavior
                [IntPtr],
               
                # [IntPtr]::Zero // Uint.makeByRef[]
                # Legit, no flags, can be 0x0 -> if (param_2 == (uint *)0x0) {uVar4 = 0;}
                [IntPtr],
               
                [IntPtr],                      # ModuleFileName Pointer (from RtlCreateUnicodeString)
                [IntPtr].MakeByRefType()       # out ModuleHandle
            ) },
            @{ Name = "LdrUnLoadDll";  Dll = "ntdll.dll";     ReturnType = [UInt32];      Parameters = [Type[]]@(
                [IntPtr]                       # ModuleHandle (PVOID*)
            )},
            @{
                Name       = "LdrGetProcedureAddressForCaller"
                Dll        = "ntdll.dll"
                ReturnType = [Int64]
                Parameters = [Type[]]@(
                    [IntPtr],                  # [HMODULE] Module handle pointer
                    [IntPtr],                  # [PSTRING] Pointer to STRING struct (pass IntPtr directly, NOT [ref])
                    [int64],                   # [ULONG]   Ordinal / Flags (usually 0)
                    [IntPtr].MakeByRefType(),  # [PVOID*]  Out pointer to procedure address (pass [ref])
                    [byte],                    # [Flags]   0 or 1 (usually 0)
                    [IntPtr]                   # [Caller]  Nullable caller address, pass [IntPtr]::Zero if none
                )
            }
        )
    
        foreach ($func in $NtdllFunctions) {
            $tb.DefinePInvokeMethod($func.Name, $func.Dll,
                $Method.attributes, $Method.CallingConventions,
                $func.ReturnType, $func.Parameters,
                $Method.nativeCallConv, $Method.nativeCharSet
            ).SetImplementationFlags($Method.ImplAttributes)
        }
    
        return $tb.CreateType()
    }
    Function Init-KERNEL32 {
        $Method = [PSCustomObject]@{
            attributes         = [MethodAttributes]::Public -bor [MethodAttributes]::Static -bor [MethodAttributes]::PinvokeImpl
            CallingConventions = [CallingConventions]::Standard
            nativeCallConv     = [CallingConvention]::Winapi
            nativeCharSet      = [CharSet]::Unicode
            ImplAttributes     = [MethodImplAttributes]::PreserveSig
            TypeAttributes     = [TypeAttributes]::Public -bor [TypeAttributes]::Abstract -bor [TypeAttributes]::Sealed
        }
    
        $asm = [AppDomain]::CurrentDomain.DefineDynamicAssembly(
            (New-Object System.Reflection.AssemblyName "Kernel32API"),
            [System.Reflection.Emit.AssemblyBuilderAccess]::Run
        )
        $mod = $asm.DefineDynamicModule("Kernel32APIModule")
        $tb  = $mod.DefineType("NativeMethods", $Method.TypeAttributes)
    
        $functions = @(
            @{ Name = "FindFirstFileW"; Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@([string], [IntPtr]) },
            @{ Name = "FindNextFileW";  Dll = "KernelBase.dll"; ReturnType = [bool];   Parameters = [Type[]]@([IntPtr], [IntPtr]) },
            @{ Name = "FindClose";      Dll = "KernelBase.dll"; ReturnType = [bool];   Parameters = [Type[]]@([IntPtr]) },
            @{ Name = "LocalFree" ;     Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@([IntPtr]) },
            @{ Name = "EnumSystemFirmwareTables"; Dll = "KernelBase.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([UInt32], [IntPtr], [UInt32]) },
            @{ Name = "GetSystemFirmwareTable";   Dll = "KernelBase.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([UInt32], [UInt32], [IntPtr], [UInt32]) },
            @{ Name = "LoadLibraryExW"; Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@([string], [IntPtr], [UInt32]) },
            @{ Name = "FreeLibrary";    Dll = "KernelBase.dll"; ReturnType = [BOOL]; Parameters = [Type[]]@([IntPtr]) },
            @{ Name = "GetProcessHeap"; Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@()},
            @{ Name = "HeapFree";       Dll = "KernelBase.dll"; ReturnType = [bool]  ; Parameters = [Type[]]@([IntPtr], [uint32], [IntPtr]) },
            @{ Name = "GetProcAddress"; Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@([IntPtr], [string]) }
        )
    
        foreach ($f in $functions) {
           
            # Default to Unicode unless overridden
            $charSet = $Method.nativeCharSet
    
            # Use ANSI marshaling only for GetProcAddress
            if ($f.Name -eq 'GetProcAddress') {
                $charSet = [CharSet]::Ansi
            }
    
            $tb.DefinePInvokeMethod(
                $f.Name, $f.Dll,
                $Method.attributes, $Method.CallingConventions,
                $f.ReturnType, $f.Parameters,
                $Method.nativeCallConv, $charSet
            ).SetImplementationFlags($Method.ImplAttributes)
        }
    
        return $tb.CreateType()
    }
    
    <#
    
         *********************
    
          !Managed Api &
                 Com Warper.!
            -  Helper's -
    
         *********************
    #>
    function Process-Parameters {
        param (
            [Parameter(Mandatory=$true)]
            [PSCustomObject]$InterfaceSpec,
    
            [switch]$Ignore
        )
    
        # Initialize the parameters list with the base parameter (thisPtr)
        $allParams = New-Object System.Collections.Generic.List[string]
    
        if (-not $Ignore) {
           $BaseParams = "IntPtr thisPtr"
           $allParams.Add($BaseParams) # Add the base parameter (thisPtr) first
        }
    
        # Process user-provided parameters if they exist
        if (-not [STRING]::IsNullOrEmpty($InterfaceSpec.Params)) {
            # Split the user-provided parameters by comma and trim whitespace
            $userParams = $InterfaceSpec.Params.Split(',') | ForEach-Object { $_.Trim() }
           
            foreach ($param in $userParams) {
                $modifier = ""
                $typeAndName = $param
    
                # Check for 'ref' or 'out' keywords, optionally wrapped in brackets, and separate them
                if ($param -match "^\s*\[?(ref|out)\]?\s+(.+)$") {
                    $modifier = $Matches[1]                 # This will capture "ref" or "out" (e.g., if input was "[REF]", $Matches[1] will be "REF")
                    $modifier = $modifier.ToLowerInvariant() # Convert modifier to lowercase ("REF" -> "ref")
                    $typeAndName = $Matches[2]             # Extract the actual type and name
                }
    
                # Split the type and name (e.g., "uint Flags" -> "uint", "Flags")
                $parts = $typeAndName.Split(' ', 2)
                if ($parts.Length -eq 2) {
                    $type = $parts[0]
                    $name = $parts[1]
                    $fixedType = $type # Default to original type if no match
    
                    # Standardize common C# types to their correct casing using a switch statement
                    switch ($type.ToLowerInvariant()) {
                        "system.boolean" { $fixedType = "bool" }
                        "system.byte"    { $fixedType = "byte" }
                        "system.sbyte"   { $fixedType = "sbyte" }
                        "system.char"    { $fixedType = "char" }
                        "system.decimal" { $fixedType = "decimal" }
                        "system.double"  { $fixedType = "double" }
                        "system.single"  { $fixedType = "float" }
                        "system.int32"   { $fixedType = "int" }
                        "system.uint32"  { $fixedType = "uint" }
                        "system.intptr"  { $fixedType = "IntPtr" }
                        "system.uintptr" { $fixedType = "UIntPtr" }
                        "system.int64"   { $fixedType = "long" }
                        "system.uint64"  { $fixedType = "ulong" }
                        "system.int16"   { $fixedType = "short" }
                        "system.uint16"  { $fixedType = "ushort" }
                        "system.object"  { $fixedType = "object" }
                        "system.string"  { $fixedType = "string" }
                        "bool"           { $fixedType = "bool" }
                        "byte"           { $fixedType = "byte" }
                        "sbyte"          { $fixedType = "sbyte" }
                        "char"           { $fixedType = "char" }
                        "decimal"        { $fixedType = "decimal" }
                        "double"         { $fixedType = "double" }
                        "float"          { $fixedType = "float" }
                        "int"            { $fixedType = "int" }
                        "uint"           { $fixedType = "uint" }
                        "long"           { $fixedType = "long" }
                        "ulong"          { $fixedType = "ulong" }
                        "short"          { $fixedType = "short" }
                        "ushort"         { $fixedType = "ushort" }
                        "object"         { $fixedType = "object" }
                        "string"         { $fixedType = "string" }
                        "intptr"         { $fixedType = "IntPtr" }
                        "uintptr"        { $fixedType = "UIntPtr" }
                        "nint"           { $fixedType = "nint" }
                        "nuint"          { $fixedType = "nuint" }
                        "boolean"        { $fixedType = "bool" }
                        "byte"           { $fixedType = "byte" }
                        "sbyte"          { $fixedType = "sbyte" }
                        "char"           { $fixedType = "char" }
                        "decimal"        { $fixedType = "decimal" }
                        "double"         { $fixedType = "double" }
                        "single"         { $fixedType = "float" }
                        "int32"          { $fixedType = "int" }
                        "uint32"         { $fixedType = "uint" }
                        "int64"          { $fixedType = "long" }
                        "uint64"         { $fixedType = "ulong" }
                        "int16"          { $fixedType = "short" }
                        "uint16"         { $fixedType = "ushort" }
                        "object"         { $fixedType = "object" }
                        "string"         { $fixedType = "string" }
                        "Guid"           { $fixedType = "Guid" }
                    }
    
                    # Reconstruct the parameter string with the fixed type and optional modifier
                    $formattedParam = "$fixedType $name"
                    if (-not [STRING]::IsNullOrEmpty($modifier)) {
                        $formattedParam = "$modifier $formattedParam"
                    }
                    $allParams.Add($formattedParam)
                } else {
                    # If the parameter couldn't be parsed, add it as is
                    $allParams.Add($param)
                }
            }
        }
       
        # Join all processed parameters with a comma and add indentation for readability
        $Params = $allParams -join ("," + "`n" + " " * 10)
    
        return $Params
    }
    function Process-ReturnType {
        param (
            [Parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [string]$ReturnType
        )
    
        $fixedReturnType = $ReturnType
    
        switch ($ReturnType.ToLowerInvariant()) {
            "system.boolean" { $fixedReturnType = "bool" }
            "system.byte"    { $fixedReturnType = "byte" }
            "system.sbyte"   { $fixedReturnType = "sbyte" }
            "system.char"    { $fixedReturnType = "char" }
            "system.decimal" { $fixedReturnType = "decimal" }
            "system.double"  { $fixedReturnType = "double" }
            "system.single"  { $fixedReturnType = "float" }
            "system.int32"   { $fixedReturnType = "int" }
            "system.uint32"  { $fixedReturnType = "uint" }
            "system.intptr"  { $fixedReturnType = "IntPtr" }
            "system.uintptr" { $fixedReturnType = "UIntPtr" }
            "system.int64"   { $fixedReturnType = "long" }
            "system.uint64"  { $fixedReturnType = "ulong" }
            "system.int16"   { $fixedReturnType = "short" }
            "system.uint16"  { $fixedReturnType = "ushort" }
            "system.object"  { $fixedReturnType = "object" }
            "system.string"  { $fixedReturnType = "string" }
            "bool"           { $fixedReturnType = "bool" }
            "byte"           { $fixedReturnType = "byte" }
            "sbyte"          { $fixedReturnType = "sbyte" }
            "char"           { $fixedReturnType = "char" }
            "decimal"        { $fixedReturnType = "decimal" }
            "double"         { $fixedReturnType = "double" }
            "float"          { $fixedReturnType = "float" }
            "int"            { $fixedReturnType = "int" }
            "uint"           { $fixedReturnType = "uint" }
            "long"           { $fixedReturnType = "long" }
            "ulong"          { $fixedReturnType = "ulong" }
            "short"          { $fixedReturnType = "short" }
            "ushort"         { $fixedReturnType = "ushort" }
            "object"         { $fixedReturnType = "object" }
            "string"         { $fixedReturnType = "string" }
            "intptr"         { $fixedReturnType = "IntPtr" }
            "uintptr"        { $fixedReturnType = "UIntPtr" }
            "nint"           { $fixedReturnType = "nint" }
            "nuint"          { $fixedReturnType = "nuint" }
            "void"           { $fixedReturnType = "void" }
            "boolean"        { $fixedReturnType = "bool" }
            "Guid"           { $fixedReturnType = "Guid" }
            "boolean"        { $fixedReturnType = "bool" }
            "int32"          { $fixedReturnType = "int" }
            "uint32"         { $fixedReturnType = "uint" }
            "int64"          { $fixedReturnType = "long" }
            "uint64"         { $fixedReturnType = "ulong" }
            "int16"          { $fixedReturnType = "short" }
            "uint16"         { $fixedReturnType = "ushort" }
        }
    
        return $fixedReturnType
    }
    
    <#
    
         *********************
    
         !Managed Com Warper.!
          -  Example code. -
    
         *********************
    
    # netlistmgr.h
    # https://raw.githubusercontent.com/nihon-tc/Rtest/refs/heads/master/header/Microsoft%20SDKs/Windows/v7.0A/Include/netlistmgr.h
    
    # get_IsConnectedToInternet
    # https://learn.microsoft.com/en-us/windows/win32/api/netlistmgr/nf-netlistmgr-inetworklistmanager-get_isconnectedtointernet
    
    -------------------------------
    
    Clear-host
    write-host "`n`nCLSID & Propertie's [Test]`nDCB00C01-570F-4A9B-8D69-199FDBA5723B->Default->IsConnected,IsConnectedToInternet`n"
    $NetObj = "DCB00C01-570F-4A9B-8D69-199FDBA5723B" | Initialize-ComObject
    write-host "IsConnected: $($NetObj.IsConnected)"
    write-host "IsConnectedToInternet: $($NetObj.IsConnectedToInternet)"
    $NetObj | Release-ComObject
    
    -------------------------------
    
    Clear-host
    write-host "`n`nIEnumerator & Params\values [Test]`nDCB00C01-570F-4A9B-8D69-199FDBA5723B->DCB00000-570F-4A9B-8D69-199FDBA5723B->GetNetwork`n"
    [intPtr]$ppEnumNetwork = [intPtr]::Zero
    Use-ComInterface `
        -CLSID "DCB00C01-570F-4A9B-8D69-199FDBA5723B" `
        -IID "DCB00000-570F-4A9B-8D69-199FDBA5723B" `
        -Index 1 `
        -Name "GetNetwork" `
        -Return "uint" `
        -Params 'system.UINT32 Flags, out INTPTR ppEnumNetwork' `
        -Values @(1, [ref]$ppEnumNetwork)
    
    if ($ppEnumNetwork -ne [IntPtr]::Zero) {
        $networkList = $ppEnumNetwork | Receive-ComObject
        foreach ($network in $networkList) {
            "Name: $($network.GetName()), IsConnected: $($network.IsConnected())"
        }
        $networkList | Release-ComObject
    }
    
    -------------------------------
    
    Clear-host
    write-host "`n`nVoid & No Return [Test]`n17CCA47D-DAE5-4E4A-AC42-CC54E28F334A->f2dcb80d-0670-44bc-9002-cd18688730af->ShowProductKeyUI`n"
    Use-ComInterface `
        -CLSID "17CCA47D-DAE5-4E4A-AC42-CC54E28F334A" `
        -IID "f2dcb80d-0670-44bc-9002-cd18688730af" `
        -Index 3 `
        -Name "ShowProductKeyUI" `
        -Return "void"
    #>
    function Build-ComInterfaceSpec {
        param (
            [Parameter(Mandatory = $true, Position = 1)]
            [ValidateNotNullOrEmpty()]
            [ValidatePattern('^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')]
            [string]$CLSID,
    
            [Parameter(Position = 2)]
            [string]$IID,
    
            [Parameter(Mandatory = $true, Position = 3)]
            [ValidateRange(1, [int]::MaxValue)]
            [int]$Index,
    
            [Parameter(Mandatory = $true, Position = 4)]
            [ValidateNotNullOrEmpty()]
            [string]$Name,
    
            [Parameter(Mandatory = $true, Position = 5)]
            [ValidateSet(
                "bool", "byte", "sbyte", "char", "decimal", "double", "float", "int", "uint", "long",
                "ulong", "short", "ushort", "object", "string", "IntPtr", "UIntPtr", "nint", "nuint",
                "boolean", "single", "int32", "uint32", "int64", "uint64", "int16", "uint16", "void", "Guid"
            )]
            [string]$Return,
           
            [Parameter(Position = 6)]
            [string]$Params
        )
    
        if (-not [string]::IsNullOrEmpty($IID)) {
            if (-not [regex]::Match($IID,'^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')){
                throw "ERROR: $IID not match ^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$"
            }
        }
    
        # Create and return the interface specification object
        $interfaceSpec = [PSCustomObject]@{
            Index  = $Index
            Return = $Return
            Name   = $Name
            Params = if ($Params) { $Params } else { "" }
            CLSID  = $CLSID
            IID    = if ($IID) { $IID } else { "" }
        }
    
        return $interfaceSpec
    }
    function Build-ComDelegate {
        param (
            [Parameter(Mandatory=$true, ValueFromPipeline)]
            [PSCustomObject]$InterfaceSpec,
    
            [Parameter(Mandatory=$true)]
            [string]$UNIQUE_ID
        )
    
        # External function calls for Params and ReturnType
        $Params = Process-Parameters -InterfaceSpec $InterfaceSpec
        $fixedReturnType = Process-ReturnType -ReturnType $InterfaceSpec.Return
    
        # Construct the delegate code template
        $Return = @"
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate $($fixedReturnType) $($UNIQUE_ID)(
            $($Params)
        );
    "@
    
        # Define the C# namespace and using statements
        $namespace = "namespace DynamicDelegates"
        $using = "`nusing System;`nusing System.Runtime.InteropServices;`n"
       
        # Combine all parts to form the final C# code
        return "$using`n$namespace`n{`n$Return`n}`n"
    }
    function Initialize-ComObject {
        param (
            [Parameter(ValueFromPipeline, Position = 0)]
            [PSCustomObject]$InterfaceSpec,
    
            [Parameter(ValueFromPipeline, Position = 1)]
            [ValidatePattern('^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')]
            [GUID]$CLSID,
    
            [switch]
            $CreateInstance
        )
    
        if ($CLSID -and $InterfaceSpec -and $CLSID.ToString() -eq $InterfaceSpec) {
            $InterfaceSpec = $null
        }
    
        # Oppsite XOR Case, Validate it Not both
        if (-not ([bool]$InterfaceSpec -xor [bool]$CLSID)) {
            throw "Select CLSID OR $InterfaceSpec"
        }
    
        # ------ BASIC SETUP -------
    
        if ($InterfaceSpec) {
            $CLSID = [guid]$InterfaceSpec.CLSID
        }
    
        # Create COM instance
        $comObj = [Activator]::CreateInstance([type]::GetTypeFromCLSID($clsid))
        if (-not $comObj) {
            throw "Failed to create COM object for CLSID $clsid"
        }
    
        if (-not $InterfaceSpec -or $CreateInstance)  {
           return $comObj
        }
    
        # Default IID to IUnknown if not provided in InterfaceSpec
        $iid = if ($InterfaceSpec.IID) {
            [guid]$InterfaceSpec.IID
        } else {
            [guid]"00000000-0000-0000-C000-000000000046"
        }
    
        # Get IUnknown pointer from COM object
        $iUnknownPtr = [Marshal]::GetIUnknownForObject($comObj)
    
        # ------ QueryInterface Delegate -------
    
        # Get IUnknown pointer from COM object
        $iUnknownPtr = [Marshal]::GetIUnknownForObject($comObj)
    
        # Get vtable pointer from IUnknown
        $vtablePtr = [Marshal]::ReadIntPtr($iUnknownPtr)
    
        # Get QueryInterface function pointer (index 0)
        $queryInterfacePtr = [Marshal]::ReadIntPtr($vtablePtr, 0 * [IntPtr]::Size)
    
        # Define QueryInterface delegate type if not already defined
        if (-not ([Type]::GetType("QueryInterfaceDelegate"))) {
            Add-Type -TypeDefinition @"
    using System;
    using System.Runtime.InteropServices;
    
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate int QueryInterfaceDelegate(IntPtr thisPtr, ref Guid riid, out IntPtr ppvObject);
    "@ -Language CSharp -ErrorAction Stop
        }
    
        # Create QueryInterface delegate
        $queryInterface = [Marshal]::GetDelegateForFunctionPointer(
            $queryInterfacePtr, [QueryInterfaceDelegate])
    
        # ------ Continue with IID Setup -------
    
        # Call QueryInterface with the IID from above
        [IntPtr]$interfacePtr = [IntPtr]::Zero
        $hresult = $queryInterface.Invoke($iUnknownPtr, [ref]$iid, [ref]$interfacePtr)
        if ($hresult -ne 0 -or $interfacePtr -eq [IntPtr]::Zero) {
            throw "QueryInterface failed with HRESULT 0x{0:X8}" -f $hresult
        }
    
        # Read the vtable pointer of the requested interface
        $requestedVTablePtr = [Marshal]::ReadIntPtr($interfacePtr)
    
        # ------ Check if inherit *** -------
    
        $interfaces = (
            @("00000112-0000-0000-C000-000000000046", 24),  # IOleObject
            @("0000010E-0000-0000-C000-000000000046", 12),  # IDataObject
            @("0000000C-0000-0000-C000-000000000046", 13),  # IStream
            @("0000010A-0000-0000-C000-000000000046", 11),  # IPersistStorage
            @("0000000B-0000-0000-C000-000000000046", 15),  # IStorage
            @("00000003-0000-0000-C000-000000000046", 9),   # IMarshal
            @("0000010B-0000-0000-C000-000000000046", 8),   # IPersistFile
            @("00000118-0000-0000-C000-000000000046", 8),   # IOleClientSite
            @("00020400-0000-0000-C000-000000000046", 7),   # IDispatch
            @("00000139-0000-0000-C000-000000000046", 7),   # IEnumSTATPROPSTG
            @("0000013A-0000-0000-C000-000000000046", 7),   # IEnumSTATPROPSETSTG
            @("0000000D-0000-0000-C000-000000000046", 7),   # IEnumSTATSTG
            @("00000109-0000-0000-C000-000000000046", 7),   # IPersistStream
            @("00020404-0000-0000-C000-000000000046", 7),   # IEnumVARIANT
            @("00000102-0000-0000-C000-000000000046", 7),   # IEnumMoniker
            @("B196B286-BAB4-101A-B69C-00AA00341D07", 7),   # IConnectionPoint
            @("00000101-0000-0000-C000-000000000046", 7),   # IEnumString
            @("00000114-0000-0000-C000-000000000046", 5),   # IOleWindow
            @("55272A00-42CB-11CE-8135-00AA004BB851", 5),   # IPropertyBag
            @("0000010C-0000-0000-C000-000000000046", 4),   # IPersist
            @("B196B283-BAB4-101A-B69C-00AA00341D07", 4),   # IProvideClassInfo
            @("A6BC3AC0-DBAA-11CE-9DE3-00AA004BB851", 4),   # IProvideClassInfo2
            @("B196B28B-BAB4-101A-B69C-00AA00341D07", 4),   # ISpecifyPropertyPages
            @("EB5E0020-8F75-11D1-ACDD-00C04FC2B085", 4),   # IPersistPropertyBag
            @("B196B284-BAB4-101A-B69C-00AA00341D07", 4)    # IConnectionPointContainer
        )
    
        # Default for IUnknown
        $baseMethodOffset = 3
    
        foreach ($iface in $interfaces) {
            $iid = $iface[0]
            $totalMethods = $iface[1]
            $ptr = [IntPtr]::Zero
    
            $hr = $queryInterface.Invoke($interfacePtr, [ref]$iid, [ref]$ptr)
            if ($hr -eq 0 -and $ptr -ne [IntPtr]::Zero) {
                $baseMethodOffset = $totalMethods
                [Marshal]::Release($ptr) | Out-Null
                break
            }
        }
    
        # ------ Continue with IID Setup -------
    
        # Get function pointer for target method using methodIndex
        $methodIndex = $baseMethodOffset + ([int]$InterfaceSpec.Index - 1)
        $funcPtr = [Marshal]::ReadIntPtr($requestedVTablePtr, $methodIndex * [IntPtr]::Size)
       
        # ------ Create Function delegate type -------
    
        $timestampSuffix = (Get-Date -Format "yyyyMMddHHmmssfff")
        $simpleUniqueDelegateName = "$($InterfaceSpec.Name)$timestampSuffix"
        $delegateCode = Build-ComDelegate -InterfaceSpec $InterfaceSpec -UNIQUE_ID $simpleUniqueDelegateName
    
        Add-Type -TypeDefinition $delegateCode -Language CSharp -ErrorAction Stop
    
        # Resolve delegate type from current domain using the explicit fully qualified name
        # This line now directly constructs the fully qualified name using $simpleUniqueDelegateName
        $fullDelegateTypeName = "DynamicDelegates.$simpleUniqueDelegateName"
        $delegateType = $null
    
        # Iterate through loaded assemblies to find the type more robustly
        foreach ($asm in [AppDomain]::CurrentDomain.GetAssemblies()) {
            $foundType = $asm.GetType($fullDelegateTypeName, $false, $true)
            if ($foundType) {
                $delegateType = $foundType
                break
            }
        }
    
        if (-not $delegateType) {
            throw "Delegate type '$simpleUniqueDelegateName' not found."
        }
    
        # Create delegate instance
        $delegateInstance = [Marshal]::GetDelegateForFunctionPointer($funcPtr, $delegateType)
    
        return [PSCustomObject]@{
            ComObject        = $comObj
            IUnknownPtr      = $iUnknownPtr
            InterfacePtr     = $interfacePtr
            VTablePtr        = $requestedVTablePtr
            FunctionPtr      = $funcPtr
            DelegateType     = $delegateType
            DelegateInstance = $delegateInstance
            InterfaceSpec    = $InterfaceSpec
            MethodIndex      = $methodIndex
            DelegateCode     = $delegateCode
        }
    }
    function Receive-ComObject {
        param (
            [Parameter(Mandatory=$true, ValueFromPipeline)]
            [object]$punk
        )
    
        try {
            return [Marshal]::GetObjectForIUnknown([intPtr]$punk)
        }
        catch {
            return $punk
        }
    }
    function Invoke-ComObject {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory, ValueFromPipeline)]
            $comInterface,
    
            [Parameter(ValueFromRemainingArguments = $true)]
            $Params
        )
    
        $output = $null
        [int]$count = if ($Params.Count) {$Params.Count} else {0}
        switch ($count) {
            0  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr)
                break
                }
    
            1  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0])
                break
                }
            2  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0],$Params[1])
                break
                }
            3  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2])
                break
                }
            4  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3])
                break
                }
            5  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4])
                break
                }
            6  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5])
                break
                }
            7  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6])
                break
                }
            8  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7])
                break
                }
            9  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7],$Params[8])
                break
                }
            10  {
                $output = $comInterface.DelegateInstance.Invoke(
                    $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7],$Params[8],$Params[9])
                break
                }
        }
        return $output
    }
    function Release-ComObject {
        param (
            [Parameter(Mandatory=$true, ValueFromPipeline)]
            $comInterface
        )
    
        $ISComObject = $comInterface.GetType().Name -match '__ComObject'
        $IsPSCustomObject = $comInterface.GetType().Name -match 'PSCustomObject'
    
        if ($ISComObject) {
            [Marshal]::ReleaseComObject($comInterface) | Out-Null
        }
        if ($IsPSCustomObject) {
            try {
                if ($comInterface.ComObject) {
                    [Marshal]::ReleaseComObject($comInterface.ComObject) | Out-Null
                }
            } catch {}
    
            try {
                if ($comInterface.IUnknownPtr -and $comInterface.IUnknownPtr -ne [IntPtr]::Zero) {
                    [Marshal]::Release($comInterface.IUnknownPtr) | Out-Null
                }
            } catch {}
    
            try {
                if ($comInterface.InterfacePtr -and $comInterface.InterfacePtr -ne [IntPtr]::Zero) {
                    [Marshal]::Release($comInterface.InterfacePtr) | Out-Null
                }
            } catch {}
    
            # Cleanup
            $comInterface.ComObject        = $null
            $comInterface.DelegateInstance = $null
            $comInterface.VTablePtr        = $null
            $comInterface.FunctionPtr      = $null
            $comInterface.DelegateType     = $null
            $comInterface.InterfaceSpec    = $null
            $comInterface.IUnknownPtr      = [IntPtr]::Zero
            [GC]::Collect()
            [GC]::WaitForPendingFinalizers()
        }
    }
    function Use-ComInterface {
        param (
            [Parameter(Mandatory = $true, Position = 1)]
            [ValidateNotNullOrEmpty()]
            [ValidatePattern('^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')]
            [string]$CLSID,
    
            [Parameter(Position = 2)]
            [ValidatePattern('^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')]
            [string]$IID,
    
            [Parameter(Mandatory = $true, Position = 3)]
            [ValidateRange(1, [int]::MaxValue)]
            [int]$Index,
    
            [Parameter(Mandatory = $true, Position = 4)]
            [ValidateNotNullOrEmpty()]
            [string]$Name,
    
            [Parameter(Mandatory = $true, Position = 5)]
            [ValidateSet(
                "bool", "byte", "sbyte", "char", "decimal", "double", "float", "int", "uint", "long",
                "ulong", "short", "ushort", "object", "string", "IntPtr", "UIntPtr", "nint", "nuint",
                "boolean", "single", "int32", "uint32", "int64", "uint64", "int16", "uint16", "void"
            )]
            [string]$Return,
           
            [Parameter(Position = 6)]
            [string]$Params,
    
            [Parameter(Position = 7)]
            [object[]]$Values
        )
    
        # 1. Build the interface specification
        $interfaceSpec = Build-ComInterfaceSpec `
            -CLSID $CLSID  `
            -IID $IID  `
            -Index $Index  `
            -Name $Name  `
            -Return $Return  `
            -Params $Params
    
        # 2. Initialize COM object
        $comObj = $interfaceSpec | Initialize-ComObject
    
        try {
            # 3. Invoke the method
            $result = $comObj | Invoke-ComObject -Params $Values
    
            # Return whatever the method returned (including HRESULT or actual return value)
            return $result
        }
        finally {
            # 4. Always release COM object
            $comObj | Release-ComObject
        }
    }
    
    <#
    
         *********************
    
         !Managed Api Warper.!
          -  Example code. -
    
         *********************
    
    Clear-Host
    Write-Host
    Invoke-UnmanagedMethod `
        -Dll "kernel32.dll" `
        -Function "Beep" `
        -Return "bool" `
        -Params "uint dwFreq, uint dwDuration" `
        -Values @(750, 300)  # 750 Hz beep for 300 ms
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Clear-Host
    Write-Host
    $buffer = New-IntPtr -Size 256
    $result = Invoke-UnmanagedMethod `
      -Dll "kernel32.dll" `
      -Function "GetComputerNameA" `
      -Return "bool" `
      -Params "IntPtr lpBuffer, ref uint lpnSize" `
      -Values @($buffer, [ref]256)
    
    if ($result) {
        $computerName = [Marshal]::PtrToStringAnsi($buffer)
        Write-Host "Computer Name: $computerName"
    } else {
        Write-Host "Failed to get computer name"
    }
    New-IntPtr -hHandle $buffer -Release
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Clear-Host
    Write-Host
    
    # ZwQuerySystemInformation
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677
    
    # SYSTEM_PROCESS_INFORMATION structure
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/process.htm
    
    # ZwQuerySystemInformation
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677
    
    # SYSTEM_BASIC_INFORMATION structure
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi/system_basic_information.htm
    
    # Step 1: Get required buffer size
    $ReturnLength = 0
    $dllResult = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(0, [IntPtr]::Zero, 0, [ref]$ReturnLength)
    
    # Allocate buffer (add some extra room just in case)
    $infoBuffer = New-IntPtr -Size $ReturnLength
    
    # Step 2: Actual call with allocated buffer
    $result = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(0, $infoBuffer, $ReturnLength, [ref]$ReturnLength)
    
    if ($result -ne 0) {
        Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)"
        Parse-ErrorMessage -MessageId $result
        New-IntPtr -hHandle $infoBuffer -Release
        return
    }
    
    # Parse values from the structure
    $sysBasicInfo = [PSCustomObject]@{
        PageSize                     = [Marshal]::ReadInt32($infoBuffer,  0x08)
        NumberOfPhysicalPages        = [Marshal]::ReadInt32($infoBuffer,  0x0C)
        LowestPhysicalPageNumber     = [Marshal]::ReadInt32($infoBuffer,  0x10)
        HighestPhysicalPageNumber    = [Marshal]::ReadInt32($infoBuffer,  0x14)
        AllocationGranularity        = [Marshal]::ReadInt32($infoBuffer,  0x18)
        MinimumUserModeAddress       = [Marshal]::ReadIntPtr($infoBuffer, 0x20)
        MaximumUserModeAddress       = [Marshal]::ReadIntPtr($infoBuffer, 0x28)
        ActiveProcessorsAffinityMask = [Marshal]::ReadIntPtr($infoBuffer, 0x30)
        NumberOfProcessors           = [Marshal]::ReadByte($infoBuffer,   0x38)
    }
    
    # Step 1: Get required buffer size
    $ReturnLength = 0
    $dllResult = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(5, [IntPtr]::Zero, 0, [ref]$ReturnLength)
    
    # Allocate buffer (add some extra room just in case)
    $ReturnLength += 200
    $procBuffer = New-IntPtr -Size $ReturnLength
    
    # Step 2: Actual call with allocated buffer
    $result = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(5, $procBuffer, $ReturnLength, [ref]$ReturnLength)
    
    if ($result -ne 0) {
        Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)"
        Parse-ErrorMessage -MessageId $result
        New-IntPtr -hHandle $procBuffer -Release
        return
    }
    
    $offset = 0
    $processList = @()
    
    while ($true) {
        try {
            $entryPtr = [IntPtr]::Add($procBuffer, $offset)
            $nextOffset = [Marshal]::ReadInt32($entryPtr, 0x00)
    
            $namePtr = [Marshal]::ReadIntPtr($entryPtr, 0x38 + [IntPtr]::Size)
            $processName = if ($namePtr -ne [IntPtr]::Zero) {
                [Marshal]::PtrToStringUni($namePtr)
            } else {
                "[System]"
            }
    
            $procObj = [PSCustomObject]@{
                ProcessId       = [Marshal]::ReadIntPtr($entryPtr, 0x50)
                ProcessName     = $processName
                NumberOfThreads = [Marshal]::ReadInt32($entryPtr, 0x04)
            }
    
            $processList += $procObj
    
            if ($nextOffset -eq 0) { break }
            $offset += $nextOffset
        } catch {
            Write-Host "Parsing error at offset $offset. Stopping."
            break
        }
    }
    
    New-IntPtr -hHandle $infoBuffer -Release
    New-IntPtr -hHandle $procBuffer -Release
    
    $sysBasicInfo | Format-List
    $processList | Sort-Object ProcessName | Format-Table ProcessId, ProcessName, NumberOfThreads -AutoSize
    #>
    function Build-ApiDelegate {
        param (
            [Parameter(Mandatory=$true, ValueFromPipeline)]
            [PSCustomObject]$InterfaceSpec,
    
            [Parameter(Mandatory=$true)]
            [string]$UNIQUE_ID
        )
    
        $namespace = "namespace DynamicDelegates"
        $using = "`nusing System;`nusing System.Runtime.InteropServices;`n"
        $Params = Process-Parameters -InterfaceSpec $InterfaceSpec -Ignore
        $fixedReturnType = Process-ReturnType -ReturnType $InterfaceSpec.Return
        $Return = @"
        [UnmanagedFunctionPointer(CallingConvention.$($InterfaceSpec.CallingType))]
        public delegate $($fixedReturnType) $($UNIQUE_ID)(
            $($Params)
        );
    "@
    
        return "$using`n$namespace`n{`n$Return`n}`n"
    }
    function Build-ApiInterfaceSpec {
        param (
            [Parameter(Mandatory = $true, Position = 1)]
            [ValidateNotNullOrEmpty()]
            [string]$Dll,
    
            [Parameter(Mandatory = $true, Position = 2)]
            [ValidateNotNullOrEmpty()]
            [string]$Function,
    
            [Parameter(Mandatory = $true, Position = 3)]
            [ValidateSet("StdCall", "Cdecl")]
            [string]$CallingConvention = "StdCall",
    
            [Parameter(Mandatory = $true, Position = 4)]
            [ValidateNotNullOrEmpty()]
            [ValidateSet(
                "bool", "byte", "sbyte", "char", "decimal", "double", "float", "int", "uint", "long",
                "ulong", "short", "ushort", "object", "string", "IntPtr", "UIntPtr", "nint", "nuint",
                "boolean", "single", "int32", "uint32", "int64", "uint64", "int16", "uint16", "void", "Guid"
            )]
            [string]$Return,
    
            [Parameter(Mandatory = $false, Position = 5)]
            [string]$Params
        )
    
        return [PSCustomObject]@{
            Dll     = $Dll
            Function= $Function
            Return  = $Return
            Params  = $Params
            CallingType = $CallingConvention
        }
    }
    function Initialize-ApiObject {
        param (
            [Parameter(Mandatory=$true, ValueFromPipeline)]
            [PSCustomObject]$ApiSpec
        )
       
        $hModule = [IntPtr]::Zero
        $BaseAddress = Ldr-LoadDll -dwFlags SEARCH_SYS32 -dll $ApiSpec.Dll
        if ($BaseAddress -ne $null -and $BaseAddress -ne [IntPtr]::Zero) {
            $hModule = [IntPtr]$BaseAddress
        }
    
        if ($hModule -eq [IntPtr]::Zero) {
            throw "Failed to load DLL: $($ApiSpec.Dll)"
        }
    
        $funcAddress = [IntPtr]::Zero
        $AnsiPtr = Init-NativeString -Value $ApiSpec.Function -Encoding Ansi
        $hresult = $Global:ntdll::LdrGetProcedureAddressForCaller(
            $hModule, $AnsiPtr, 0, [ref]$funcAddress, 0, [IntPtr]::Zero)
        Free-NativeString -StringPtr $AnsiPtr
        if ($funcAddress -eq [IntPtr]::Zero -or $hresult -ne 0) {
            throw "Failed to find function: $($ApiSpec.Function)"
        }
    
        # Build delegate
        $uniqueName = "$($ApiSpec.Function)Api$(Get-Random)"
        $delegateCode = Build-ApiDelegate -InterfaceSpec $ApiSpec -UNIQUE_ID $uniqueName
    
        Add-Type -TypeDefinition $delegateCode -Language CSharp -ErrorAction Stop
        $delegateType = [AppDomain]::CurrentDomain.GetAssemblies() |
            ForEach-Object { $_.GetType("DynamicDelegates.$uniqueName", $false, $true) } |
            Where-Object { $_ } |
            Select-Object -First 1
    
        if (-not $delegateType) {
            throw "Failed to get delegate type for $uniqueName"
        }
    
        $delegate = [Marshal]::GetDelegateForFunctionPointer(
            $funcAddress, $delegateType)
    
        return [PSCustomObject]@{
            Dll           = $ApiSpec.Dll
            Function      = $ApiSpec.Function
            FunctionPtr   = $funcAddress
            DelegateInstance = $delegate
            DelegateType  = $delegateType
            DelegateCode  = $delegateCode
        }
    }
    function Invoke-ApiObject {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory, ValueFromPipeline)]
            $ApiInterface,
    
            [Parameter(ValueFromRemainingArguments = $true)]
            $Params
        )
    
        $output = $null
        [int]$count = if ($Params.Count) {$Params.Count} else {0}
        switch ($count) {
            0  {
                $output = $ApiInterface.DelegateInstance.Invoke()
                break
                }
    
            1  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0])
                break
                }
            2  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0],$Params[1])
                break
                }
            3  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0],$Params[1],$Params[2])
                break
                }
            4  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0],$Params[1],$Params[2],$Params[3])
                break
                }
            5  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0],$Params[1],$Params[2],$Params[3],$Params[4])
                break
                }
            6  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5])
                break
                }
            7  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6])
                break
                }
            8  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7])
                break
                }
            9  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7],$Params[8])
                break
                }
            10  {
                $output = $ApiInterface.DelegateInstance.Invoke(
                    $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7],$Params[8],$Params[9])
                break
                }
        }
        return $output
    }
    function Release-ApiObject {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true, ValueFromPipeline)]
            [PSCustomObject]$ApiObject
        )
    
        process {
            try {
                $ApiObject.DelegateInstance = $null
                $ApiObject.DelegateType = $null
                $ApiObject.FunctionPtr = [IntPtr]::Zero
                $ApiObject.DelegateCode = $null
                $ApiObject.Dll = $null
                $ApiObject.Function = $null
                [GC]::Collect()
                [GC]::WaitForPendingFinalizers()
    
            } catch {
                Write-Warning "Failed to release ApiObject: $_"
            }
        }
    }
    function Invoke-UnmanagedMethod {
        param (
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [string]$Dll,
    
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [string]$Function,
    
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [string]$Return,
    
            [Parameter(Mandatory = $false)]
            [string]$Params,
    
            [Parameter(Mandatory = $false)]
            [ValidateSet("StdCall", "Cdecl")]
            [string]$CallingConvention,
    
            [Parameter(Mandatory = $false)]
            [object[]]$Values
        )
    
        # Detect platform
        if (-not $CallingConvention) {
            if ([IntPtr]::Size -eq 8) {
                $CallingConvention = "StdCall"
            }
            else {
                $CallingConvention = "StdCall"
            }
        }
    
        $apiSpec = Build-ApiInterfaceSpec -Dll $Dll  `
            -Function $Function  `
            -Return $Return  `
            -CallingConvention $CallingConvention `
            -Params $Params
    
        $apiObj = $apiSpec | Initialize-ApiObject
    
        try {
            return $apiObj | Invoke-ApiObject -Params $Values
        }
        finally {
            $apiObj | Release-ApiObject
        }
    }
    
    <#
    .HELPERS
    UnicodeString function helper,
    just for testing purpose
    
    +++ Struct Info +++
    
    typedef struct _UNICODE_STRING {
      USHORT Length; [ushort = 2]
      USHORT MaximumLength; [ushort = 2]
      ** in x64 enviroment Add 4 byte's padding **
      PWSTR  Buffer; [IntPtr].Size
    } UNICODE_STRING, *PUNICODE_STRING;
    
    Buffer Offset == [IntPtr].Size { x86=4, x64=8 }
    
    +++ Test Code +++
    
    Clear-Host
    Write-Host
    
    $unicodeStringPtr = Init-NativeString -Value 99 -Encoding Unicode
    Parse-NativeString -StringPtr $unicodeStringPtr -Encoding Unicode
    Free-NativeString -StringPtr $unicodeStringPtr
    
    $ansiStringPtr = Init-NativeString -Value 99 -Encoding Ansi
    Parse-NativeString -StringPtr $ansiStringPtr -Encoding Ansi
    Free-NativeString -StringPtr $ansiStringPtr
    
    $unicodeStringPtr = [IntPtr]::Zero
    $unicodeStringPtr = Manage-UnicodeString -Value 'data123'
    Parse-UnicodeString -unicodeStringPtr $unicodeStringPtr
    Manage-UnicodeString -UnicodeStringPtr $unicodeStringPtr -Release
    #>
    function Init-NativeString {
        param (
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [string]$Value,
    
            [Parameter(Mandatory = $false)]
            [ValidateSet('Ansi', 'Unicode')]
            [string]$Encoding = 'Ansi'
        )
    
        $stringPtr = New-IntPtr -Size 16
    
        if ($Encoding -eq 'Ansi') {
            $Length = [System.Text.Encoding]::ASCII.GetByteCount($Value)
            if ($Length -ge 0xFFFE) {
                $Length = 0xFFFC
            }
    
            $bufferPtr = [Marshal]::StringToHGlobalAnsi($Value)
            $maxLength = $Length + 1
        }
        else {
            $Length = $Value.Length * 2
            if ($Length -ge 0xFFFE) {
                $Length = 0xFFFC
            }
            $bufferPtr = [Marshal]::StringToHGlobalUni($Value)
            $maxLength = $Length + 2
        }
    
        [Marshal]::WriteInt16($stringPtr, 0, $Length)
        [Marshal]::WriteInt16($stringPtr, 2, $maxLength)
        [Marshal]::WriteIntPtr($stringPtr, [IntPtr]::Size, $bufferPtr)
    
        return $stringPtr
    }
    function Parse-NativeString {
        param (
            [Parameter(Mandatory = $true)]
            [IntPtr]$StringPtr,
    
            [Parameter(Mandatory = $false)]
            [ValidateSet('Ansi', 'Unicode')]
            [string]$Encoding = 'Ansi'
        )
    
        if ($StringPtr -eq [IntPtr]::Zero) {
            return
        }
    
        $Length = [Marshal]::ReadInt16($StringPtr, 0)
        $Size   = [Marshal]::ReadInt16($StringPtr, 2)
        $BufferPtr = [Marshal]::ReadIntPtr($StringPtr, [IntPtr]::Size)
    
        if ($Encoding -eq 'Ansi') {
            # Length is number of bytes
            $Data = [Marshal]::PtrToStringAnsi($BufferPtr, $Length)
        } else {
            # Unicode, length is bytes, divide by 2 for chars
            $Data = [Marshal]::PtrToStringUni($BufferPtr, $Length / 2)
        }
    
        return [PSCustomObject]@{
            Length        = $Length
            MaximumLength = $Size
            StringData    = $Data
        }
    }
    function Free-NativeString {
        param (
            [Parameter(Mandatory = $true)]
            [IntPtr]$StringPtr
        )
       
        if ($StringPtr -eq [IntPtr]::Zero) {
            Write-Warning 'Failed to free pointer: The pointer is null'
            return
        }
    
        $ptr = [IntPtr]::Zero
        try {
            $bufferPtr = [Marshal]::ReadIntPtr($StringPtr, [IntPtr]::Size)
            if ($bufferPtr -ne [IntPtr]::Zero) {
                [Marshal]::FreeHGlobal($bufferPtr)
            }
            else {
                Write-Warning 'Failed to free buffer: The buffer pointer is null.'
            }
            [Marshal]::FreeHGlobal($StringPtr)
        }
        catch {
            Write-Warning 'An error occurred while attempting to free memory'
            return
        }
    }
    
    <#
    PEB structure (winternl.h)
    https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
    
    PEB_LDR_DATA structure (winternl.h)
    https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data
    
    PEB
    https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm
    
    PEB_LDR_DATA
    https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm?tx=185
    
    LDR_DATA_TABLE_ENTRY
    https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry/index.htm?tx=179,185
    
    .........................
    
    typedef struct PEB {
      BYTE                          Reserved1[2];
      BYTE                          BeingDebugged;
      BYTE                          Reserved2[1];
      PVOID                         Reserved3[2];
     
      PPEB_LDR_DATA                 Ldr;
      ---> Pointer to PEB_LDR_DATA struct
    }
    
    typedef struct PEB_LDR_DATA {
     0x0C, 0x10, LIST_ENTRY InLoadOrderModuleList;
     0x14, 0x20, LIST_ENTRY InMemoryOrderModuleList;
     0x1C, 0x30, LIST_ENTRY InInitializationOrderModuleList;
      ---> Pointer to LIST_ENTRY struct
    
    }
    
    typedef struct LIST_ENTRY {
       struct LDR_DATA_TABLE_ENTRY *Flink;
       ---> Pointer to next _LDR_DATA_TABLE_ENTRY struct
    }
    
    typedef struct LDR_DATA_TABLE_ENTRY {
        0x00 0x00 LIST_ENTRY InLoadOrderLinks;
        0x08 0x10 LIST_ENTRY InMemoryOrderLinks;
        0x10 0x20 LIST_ENTRY InInitializationOrderLinks;
        ---> Actual LIST_ENTRY struct, Not Pointer
    
        ...
        PVOID DllBase;
        PVOID EntryPoint;
        ...
        UNICODE_STRING FullDllName;
    }
    
    .........................
    
    ** x64 system example **
    You don't get Pointer to [LDR_DATA_TABLE_ENTRY] Offset 0x0, it depend
    So, you need to consider, [LinkPtr] & [+Data Offset -0x00\0x10\0x20] -> Actual Offset of Data to read
    
    [PEB_LDR_DATA] & 0x10 -> Read Pointer -> \ List Head [LIST_ENTRY]->Flink \ -> [LDR_DATA_TABLE_ENTRY]->[LIST_ENTRY]->0x00 [AKA] InLoadOrderLinks           [& Repeat]
    [PEB_LDR_DATA] & 0x20 -> Read Pointer -> \ List Head [LIST_ENTRY]->Flink \ -> [LDR_DATA_TABLE_ENTRY]->[LIST_ENTRY]->0x10 [AKA] InMemoryOrderLinks         [& Repeat]
    [PEB_LDR_DATA] & 0x30 -> Read Pointer -> \ List Head [LIST_ENTRY]->Flink \ -> [LDR_DATA_TABLE_ENTRY]->[LIST_ENTRY]->0x20 [AKA] InInitializationOrderLinks [& Repeat]
    
    
    .........................
    
    - (*PPEB_LDR_DATA)->InMemoryOrderModuleList -> [LIST_ENTRY] head
    - each [LIST_ENTRY] contain [*flink], which point to next [LIST_ENTRY]
    - [LDR_DATA_TABLE] is also [LIST_ENTRY], first offset 0x0 is [LIST_ENTRY],
      Like this -> (LDR_DATA_TABLE_ENTRY *) = (LIST_ENTRY *)
    
    the result of this is!
    [LIST_ENTRY] head, is actually [LIST_ENTRY] And not [LDR_DATA_TABLE]
    only used to start the Loop chain, to Read the next [LDR_DATA_TABLE]
    and than, read next [LDR_DATA_TABLE] item from [0x0  LIST_ENTRY] InLoadOrderLinks
    which is actually [0x0] flink* -> pointer to another [LDR_DATA_TABLE]
    
    C Code ->
    
    LIST_ENTRY* head = &Peb->Ldr->InMemoryOrderModuleList;
    LIST_ENTRY* current = head->Flink;
    
    while (current != head) {
        LDR_DATA_TABLE_ENTRY* module = (LDR_DATA_TABLE_ENTRY*)current;
        wprintf(L"Loaded Module: %wZ\n", &module->FullDllName);
        current = current->Flink;
    }
    
    Diagram ->
    
    [PEB_LDR_DATA]
     --- InMemoryOrderModuleList (LIST_ENTRY head)
            - Flink
    [LDR_DATA_TABLE_ENTRY]
     --- LIST_ENTRY InLoadOrderLinks (offset 0x0)
     --- DllBase, EntryPoint, SizeOfImage, etc.
            - Flink
    Another [LDR_DATA_TABLE_ENTRY]
    
    .........................
    
    Managed code ? sure.
    [Process]::GetCurrentProcess().Modules
    
    #>
    function Read-MemoryValue {
        param (
            [Parameter(Mandatory)]
            [IntPtr]$LinkPtr,
    
            [Parameter(Mandatory)]
            [int]$Offset,
    
            [Parameter(Mandatory)]
            [ValidateSet("IntPtr","Int16", "UInt16", "Int32", "UInt32", "UnicodeString")]
            [string]$Type
        )
    
        # Calculate the actual address to read from:
        $Address = [IntPtr]::Add($LinkPtr, $Offset)
    
        try {
            switch ($Type) {
                "IntPtr" {
                    return [Marshal]::ReadIntPtr($Address)
                }
                "Int16" {
                    return [Marshal]::ReadInt16($Address)
                }
                "UInt16" {
                    $rawValue = [Marshal]::ReadInt16($Address)
                    return [UInt16]($rawValue -band 0xFFFF)
                }
                "Int32" {
                    return [Marshal]::ReadInt32($Address)
                }
                "UInt32" {
                    return [UInt32]([Marshal]::ReadInt32($Address))
                }
                "UnicodeString" {
                    $length = [Marshal]::ReadInt16($Address)
                    if ($length -le 0 -or $length % 2 -ne 0 -or $length -gt 4096) {
                        return ""
                    }
                    $pointerSize = [IntPtr]::Size
                    $bufferOffset = if ($pointerSize -eq 8) { $Offset + 8 } else { $Offset + 4 }
    
                    $bufferPtr = if ($pointerSize -eq 8) {
                        [Marshal]::ReadIntPtr([IntPtr]::Add($LinkPtr, $bufferOffset))
                    } else {
                        $ptrVal = [Marshal]::ReadInt32([IntPtr]::Add($LinkPtr, $bufferOffset))
                        if ($ptrVal -eq 0) { return "" }
                        [IntPtr]::new($ptrVal)
                    }
    
                    if ($bufferPtr -eq [IntPtr]::Zero) {
                        return ""
                    }
    
                    $bytes = New-Object byte[] $length
                    [Marshal]::Copy($bufferPtr, $bytes, 0, $length)
                    return [Text.Encoding]::Unicode.GetString($bytes)
                }
            }
        }
        catch {
            Write-Warning "Failed to read memory value at offset 0x$([Convert]::ToString($Offset,16)) (Type: $Type). Error: $_"
            return $null
        }
    }
    function Get-LoadedModules {
        param (
            [Parameter(Mandatory=$true)]
            [ValidateSet("Load", "Memory", "Init")]
            [string]$SortType = "Memory",
    
            [Parameter(Mandatory=$false)]
            [IntPtr]$PebPtr = [IntPtr]::Zero
        )
    
        Enum PebOffset_x86 {
            ldrOffset = 0x0C
            InLoadOrderModuleList = 0x0C
            InMemoryOrderModuleList = 0x14
            InInitializationOrderModuleList = 0x1C
            InLoadOrderLinks = 0x00
            InMemoryOrderLinks = 0x08
            InInitializationOrderLinks = 0x10
            DllBase = 0x18
            EntryPoint = 0x1C
            SizeOfImage = 0x20
            FullDllName = 0x24
            BaseDllName = 0x2C
            Flags = 0x34
           
            # ObsoleteLoadCount
            LoadCount = 0x38
    
            LoadReason = 0x94
            ReferenceCount = 0x9C
        }
        Enum PebOffset_x64 {
            ldrOffset = 0x18
            InLoadOrderModuleList = 0x10
            InMemoryOrderModuleList = 0x20
            InInitializationOrderModuleList = 0x30
            InLoadOrderLinks = 0x00
            InMemoryOrderLinks = 0x10
            InInitializationOrderLinks = 0x20
            DllBase = 0x30
            EntryPoint = 0x38
            SizeOfImage = 0x40
            FullDllName = 0x48
            BaseDllName = 0x58
            Flags = 0x68
    
            # ObsoleteLoadCount
            LoadCount = 0x6C
    
            LoadReason = 0x010C
            ReferenceCount = 0x0114
        }
        if ([IntPtr]::Size -eq 8) {
            $PebOffset = [PebOffset_x64]
        } else {
            $PebOffset = [PebOffset_x86]
        }
    
        # Get Peb address pointr
        if ($PebPtr -eq [IntPtr]::Zero) {
            $PebPtr = $Global:ntdll::RtlGetCurrentPeb()
            if ($PebPtr -eq [IntPtr]::Zero) {
                throw "Failed to get PEB pointer."
            }
        }
       
        # Get PEB->Ldr address pointr
        $ldrPtr = [Marshal]::ReadIntPtr(
            [IntPtr]::Add($PebPtr, $PebOffset::ldrOffset.value__))
        if ($ldrPtr -eq [IntPtr]::Zero) {
            throw "PEB->Ldr is null. Cannot continue."
        }
    
        try {
           
            # Storage to hold module list
            $modules = @()
    
            # Determine offsets based on sorting type
            switch ($SortType) {
                "Load" {
                    $ModuleListOffset   = $PebOffset::InLoadOrderModuleList.value__
                    $LinkOffsetInEntry  = $PebOffset::InLoadOrderLinks.value__
                }
                "Memory" {
                    $ModuleListOffset   = $PebOffset::InMemoryOrderModuleList.value__
                    $LinkOffsetInEntry  = $PebOffset::InMemoryOrderLinks.value__
                }
                "Init" {
                    $ModuleListOffset   = $PebOffset::InInitializationOrderModuleList.value__
                    $LinkOffsetInEntry  = $PebOffset::InInitializationOrderLinks.value__
                }
            }
    
            <#
                PEB_LDR_DATA->?*ModuleList --> [LIST_ENTRY] Head
                Results depend on List Type, by user choice.
            #>
            $ListHeadPtr = [IntPtr]::Add($ldrPtr, $ModuleListOffset)
    
            <#
                *Flink --> First [LDR_DATA_TABLE_ENTRY] -> Offset of:
                InLoadOrderLinks -or InMemoryOrderLinks -or InInitializationOrderLinks
                So, you dont get base address of [LDR_DATA_TABLE_ENTRY], it shifted, depend, result from:
    
                * InLoadOrderLinks = if ([IntPtr]::Size -eq 8) { 0x00 } else { 0x00 }
                * InMemoryOrderLinks = if ([IntPtr]::Size -eq 8) { 0x10 } else { 0x08 }
                * InInitializationOrderLinks = if ([IntPtr]::Size -eq 8) { 0x20 } else { 0x10 }
            #>
            $NextLinkPtr = [Marshal]::ReadIntPtr($ListHeadPtr)
    
            <#
               Shift offset, to Fix BaseAddress MAP,
               so, calculate -> NextLinkPtr & (+Offset -StructBaseOffset) == Data Object *Fixed* Offset
               will be used later when call Read-MemoryValue function.
            #>
            $PebOffsetMap = @{}
            foreach ($Name in ("DllBase", "EntryPoint", "SizeOfImage", "FullDllName", "BaseDllName", "Flags", "LoadCount" , "LoadReason", "ReferenceCount")) {
                $PebOffsetMap[$Name] = $PebOffset.GetField($Name).GetRawConstantValue() - $LinkOffsetInEntry
            }
    
            # Start parse Data
    
            enum LdrFlagsMap {
                PackagedBinary         = 0x00000001
                MarkedForRemoval       = 0x00000002
                ImageDll               = 0x00000004
                LoadNotificationsSent  = 0x00000008
                TelemetryEntryProcessed= 0x00000010
                ProcessStaticImport    = 0x00000020
                InLegacyLists          = 0x00000040
                InIndexes              = 0x00000080
                ShimDll                = 0x00000100
                InExceptionTable       = 0x00000200
                LoadInProgress         = 0x00001000
                LoadConfigProcessed    = 0x00002000
                EntryProcessed         = 0x00004000
                ProtectDelayLoad       = 0x00008000
                DontCallForThreads     = 0x00040000
                ProcessAttachCalled    = 0x00080000
                ProcessAttachFailed    = 0x00100000
                CorDeferredValidate    = 0x00200000
                CorImage               = 0x00400000
                DontRelocate           = 0x00800000
                CorILOnly              = 0x01000000
                ChpeImage              = 0x02000000
                Redirected             = 0x10000000
                CompatDatabaseProcessed= 0x80000000
            }
    
            enum LdrLoadReasonMap {
                StaticDependency = 0
                StaticForwarderDependency = 1
                DynamicForwarderDependency = 2
                DelayloadDependency = 3
                DynamicLoad = 4
                AsImageLoad = 5
                AsDataLoad = 6
                EnclavePrimary = 7
                EnclaveDependency = 8
                Unknown = -1
            }
    
            $flagsValue = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['Flags'] -Type UInt32
            $allFlagValues = [Enum]::GetValues([LdrFlagsMap])
            $FlagNames = $allFlagValues | ? {  ($flagsValue -band [int]$_) -ne 0  } | ForEach-Object { $_.ToString() }
            $ReadableFlags = if ($FlagNames.Count -gt 0) {  $FlagNames -join ", "  } else {  "None"  }
    
            $LoadReasonValue = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['LoadReason'] -Type UInt32
            try {
                $LoadReasonName = [LdrLoadReasonMap]$LoadReasonValue
            } catch {
                $LoadReasonName = "Unknown ($LoadReasonValue)"
            }
    
            do {
                $modules += [PSCustomObject]@{
                    BaseAddress = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['DllBase']     -Type IntPtr
                    EntryPoint  = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['EntryPoint']  -Type IntPtr
                    SizeOfImage = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['SizeOfImage'] -Type UInt32
                    FullDllName = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['FullDllName'] -Type UnicodeString
                    ModuleName  = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['BaseDllName'] -Type UnicodeString
                    Flags       = $ReadableFlags
                    LoadReason  = $LoadReasonName
                    ReferenceCount = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['ReferenceCount'] -Type UInt16
                    LoadAsData  = $false
                }
    
                <#
                    [LIST_ENTRY], 0x? -> [LIST_ENTRY] ???OrderLinks
                    *Flink --> Next [LIST_ENTRY] -> [LDR_DATA_TABLE_ENTRY]
                    So, we Read Item Pointer for next [LIST_ENTRY], AKA [LDR_DATA_TABLE_ENTRY]
                    but, again, not BaseAddress of [LDR_DATA_TABLE_ENTRY], it depend on user Req.
                    [LDR_DATA_TABLE_ENTRY] --> 0x0 -> [LIST_ENTRY], [LIST_ENTRY], [LIST_ENTRY], [Actuall Data]
                #>
               
                $NextLinkPtr = [Marshal]::ReadIntPtr($NextLinkPtr)
    
            } while ($NextLinkPtr -ne $ListHeadPtr)
        }
        catch {
            Write-Warning "Failed to enumerate modules. Error: $_"
        }
    
        return $modules
    }
    
    <#
        LdrLoadDll Data Convert Helper
        ------------------------------
       
        >>>>>>>>>>>>>>>>>>>>>>>>>>>
        API-SPY --> SLUI 0x2a ERROR
        >>>>>>>>>>>>>>>>>>>>>>>>>>>
    
        0x00000010 - LOAD_IGNORE_CODE_AUTHZ_LEVEL
        LdrLoadDll(1,[Ref]0, 0x000000cae83fda50, 0x000000cae83fda98)
       
        0x00000008 - LOAD_WITH_ALTERED_SEARCH_PATH          
        LdrLoadDll(9,[Ref]0, 0x000000cae7fee930, 0x000000cae7fee978)
               
        0x00000800 - LOAD_LIBRARY_SEARCH_SYSTEM32
        LdrLoadDll(2049, [Ref]0, 0x000000cae83fed00, 0x000000cae83fed48 )
       
        0x00002000 -bor 0x00000008 - LOAD_LIBRARY_SAFE_CURRENT_DIRS & LOAD_WITH_ALTERED_SEARCH_PATH
        LdrLoadDll(8201, [Ref]0, 0x000000cae85fcbb0, 0x000000cae85fcbf8 )
    
        >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        HMODULE __stdcall LoadLibraryExW(LPCWSTR lpLibFileName,HANDLE hFile,DWORD dwFlags)
        >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    
        if ((dwFlags & 0x62) == 0) {
        local_res8[0] = 0;
        if ((dwFlags & 1) != 0) {
            local_res8[0] = 2;
            uVar3 = 2;
        }
        if ((char)dwFlags < '\0') {
            uVar3 = uVar3 | 0x800000;
            local_res8[0] = uVar3;
        }
        if ((dwFlags & 4) != 0) {
            uVar3 = uVar3 | 4;
            local_res8[0] = uVar3;
        }
        if ((dwFlags >> 0xf & 1) != 0) {
            local_res8[0] = uVar3 | 0x80000000;
        }
        iVar1 = LdrLoadDll(dwFlags & 0x7f08 | 1,local_res8,local_28,&local_res20);
        }
    
        >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        void LdrLoadDll(ulonglong param_1,uint *param_2,uint *param_3,undefined8 *param_4)
        >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    
          if (param_2 == (uint *)0x0) {
            uVar4 = 0;
          }
          else {
            uVar4 = (*param_2 & 4) * 2;
            uVar3 = uVar4 | 0x40;
            if ((*param_2 & 2) == 0) {
              uVar3 = uVar4;
            }
            uVar4 = uVar3 | 0x80;
            if ((*param_2 & 0x800000) == 0) {
              uVar4 = uVar3;
            }
            uVar3 = uVar4 | 0x100;
            if ((*param_2 & 0x1000) == 0) {
              uVar3 = uVar4;
            }
            uVar4 = uVar3 | 0x400000;
            if (-1 < (int)*param_2) {
              uVar4 = uVar3;
            }
          }
    
        SearchPath
        ----------
        (0x00000001 -band 0x7f08) -bor 1 // DONT_RESOLVE_DLL_REFERENCES
        (0x00000010 -band 0x7f08) -bor 1 // LOAD_IGNORE_CODE_AUTHZ_LEVEL
        (0x00000200 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_APPLICATION_DIR
        (0x00001000 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
        (0x00000100 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
        (0x00000800 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_SYSTEM32
        (0x00000400 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_USER_DIRS
        (0x00000008 -band 0x7f08) -bor 1 // LOAD_WITH_ALTERED_SEARCH_PATH
        (0x00000080 -band 0x7f08) -bor 1 // LOAD_LIBRARY_REQUIRE_SIGNED_TARGET
        (0x00002000 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SAFE_CURRENT_DIRS
    
        This --> will auto bypass to LoadLibraryEx?
        0x00000002, LOAD_LIBRARY_AS_DATAFILE
        0x00000040, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE
        0x00000020, LOAD_LIBRARY_AS_IMAGE_RESOURCE
    
        DllCharacteristics
        ------------------
        Auto deteced by function.
        According to dwFlag value,
        who provide by user.
    #>
    enum LOAD_LIBRARY {
        NO_DLL_REF = 0x00000001
        IGNORE_AUTHZ = 0x00000010
        AS_DATAFILE = 0x00000002
        AS_DATAFILE_EXCL = 0x00000040
        AS_IMAGE_RES = 0x00000020
        SEARCH_APP = 0x00000200
        SEARCH_DEFAULT = 0x00001000
        SEARCH_DLL_LOAD = 0x00000100
        SEARCH_SYS32 = 0x00000800
        SEARCH_USER = 0x00000400
        ALTERED_SEARCH = 0x00000008
        REQ_SIGNED = 0x00000080
        SAFE_CURRENT = 0x00002000
    }
    function Ldr-LoadDll {
        param (
            [Parameter(Mandatory = $true)]
            [LOAD_LIBRARY]$dwFlags,
           
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [string]$dll,
    
            [Parameter(Mandatory = $false)]
            [switch]$Log,
    
            [Parameter(Mandatory = $false)]
            [switch]$ForceNew
        )
    
        $HResults  = [IntPtr]::Zero
        $FlagsPtr  = [IntPtr]::Zero
        $stringPtr = [IntPtr]::Zero
    
        if (!$dwFlags -or $dwFlags.value__ -eq $null) {
            throw "can't access dwFlags value"
        }
    
        if ($dwFlags.value__ -lt 0) {
            throw "dwFlags Can't be less than 0"
        }
    
        if (-not $global:LoadedModules) {
            $global:LoadedModules = Get-LoadedModules -SortType Memory |
                Select-Object BaseAddress, ModuleName, LoadAsData
        }
    
        # $ForceNew == $Log ==> $false
        $ReUseHandle = !$Log -and !$ForceNew
    
        # Equivalent to: if ((dwFlags & 0x62) == 0)
        # AS_DATAFILE, AS_DATAFILE_EXCLUSIVE, AS_IMAGE_RESOURCE
        $IsDataLoad = ($dwFlags.value__ -band 0x62) -ne 0
    
        if ($ReUseHandle) {
            $dllObjList = $global:LoadedModules |
                Where-Object { $_.ModuleName -ieq $dll }
    
            if ($dllObjList) {
                if ($IsDataLoad) {
                    $dllObj = $dllObjList | Where-Object { $_.LoadAsData } | Select-Object -Last 1 -ExpandProperty BaseAddress
                } else {
                    $dllObj = $dllObjList | Where-Object { -not $_.LoadAsData } | Select-Object -Last 1 -ExpandProperty BaseAddress
                }
    
                if ($dllObj) {
                    Write-Warning "Returning reusable module object for $dll"
                    return $dllObj
                }
            }
        }
    
        try {
            $FlagsPtr = New-IntPtr -Size 4
            if ($IsDataLoad) {
               
                # Data Load -> Begin
                if ($Log) {
                    Write-host "Flags      = $($dwFlags.value__)"
                    Write-host "SearchPath = NULL"
                    Write-host "Function   = LoadLibraryExW"
                    return
                }
    
                Write-Warning 'Logging only --> LoadLibraryExW'
                $HResults = $Global:kernel32::LoadLibraryExW(
                    $dll, [IntPtr]::Zero, $dwFlags.value__)
               
                if ($HResults -ne [IntPtr]::Zero) {
                    $dllInfo = [PSCustomObject]@{
                        BaseAddress = $HResults
                        ModuleName  = $dll
                        LoadAsData  = $true
                    }
                    $global:LoadedModules += $dllInfo
                }
                return $HResults
                # Data Load -> End
    
            } else {
               
                # Normal Load -> Begin
                $uVar3 = 0
                if (($dwFlags.value__ -band 1) -ne 0) { $uVar3 = 2 }
                if (($dwFlags.value__ -band 0x80) -ne 0) { $uVar3 = $uVar3 -bor 0x800000 }
                if (($dwFlags.value__ -band 4) -ne 0) { $uVar3 = $uVar3 -bor 4 }
                if ((($dwFlags.value__ -shr 15) -band 1) -ne 0) { $uVar3 = $uVar3 -bor 0x80000000 }
                $Flags = ($dwFlags.value__ -band 0x7f08) -bor 1
                $DllCharacteristics = $uVar3
    
                if ($Log) {
                    Write-host "Flags      = $Flags"
                    Write-host "SearchPath = $DllCharacteristics"
                    Write-host "Function   = LdrLoadDll"
                    return
                }
    
                # parameter [1] Filepath
                $FilePath = [IntPtr]::new($Flags)
               
                # parameter [2] DllCharacteristics
                [Marshal]::WriteInt32(
                    $FlagsPtr, $DllCharacteristics)
               
                # parameter [3] UnicodeString
                $stringPtr = Init-NativeString -Value $dll -Encoding Unicode
               
                # Out Results
                Write-Warning 'Logging only --> LdrLoadDll'
                $null = $Global:ntdll::LdrLoadDll(
                    $FilePath,         # Flags
                    $FlagsPtr,         # NULL / [REF]Long
                    $stringPtr,        # [REF]UnicodeString
                    [ref]$HResults     # [Out]Handle
                )
                if ($HResults -ne [IntPtr]::Zero) {
                    $dllInfo = [PSCustomObject]@{
                        BaseAddress = $HResults
                        ModuleName  = $dll
                        LoadAsData  = $false
                    }
                    $global:LoadedModules += $dllInfo
                }
                return $HResults
                # Normal Load -> End
            }
        }
        catch {
        }
        finally {
            if ($FlagsPtr -ne $null -and $FlagsPtr -ne [IntPtr]::Zero) {
                New-IntPtr -hHandle $FlagsPtr -Release
            }
            if ($unicodeStringPtr -ne $null -and $unicodeStringPtr -ne [IntPtr]::Zero) {
                Free-NativeString -StringPtr $stringPtr
            }
        }
    
        return $HResults
    }
    
    <#
    .SYNOPSIS
    Manages native UNICODE_STRING memory and content for P/Invoke.
    
    .DESCRIPTION
    This function allows for the creation of new UNICODE_STRING structures,
    in-place updating of existing ones, and safe release of all associated
    unmanaged memory (both the structure and its internal string buffer)
    using low-level NTDLL APIs.
    
    .USE
    Manage-UnicodeString -Value '?'
    Manage-UnicodeString -Value '?' -UnicodeStringPtr ?
    Manage-UnicodeString -UnicodeStringPtr ? -Release
    #>
    function Manage-UnicodeString {
        param (
            [Parameter(Mandatory = $false)]
            [ValidateNotNullOrEmpty()]
            [string] $Value,
    
            [Parameter(Mandatory = $false)]
            [IntPtr] $UnicodeStringPtr = [IntPtr]::Zero,
    
            [switch] $Release
        )
    
        # Check if the pointer is valid (non-zero)
        $isValidPtr = $UnicodeStringPtr -ne [IntPtr]::Zero
    
        # Case 1: Value only ג†’ allocate and create a new string (if pointer is zero)
        if ($Value -and -not $isValidPtr -and -not $Release) {
            $unicodeStringPtr = New-IntPtr -Size 16
            $returnValue = $Global:ntdll::RtlCreateUnicodeString($unicodeStringPtr, $Value)
    
            # Check if the lowest byte is 1 (indicating success as per the C code's CONCAT71)
            if (($returnValue -band 0xFF) -ne 1) {
                    throw "Failed to create Unicode string for '$Value'. NTSTATUS return value: 0x$hexReturnValue"
            }
    
            return $unicodeStringPtr
        }
    
        # Case 2: Value + existing pointer ג†’ reuse the pointer (if pointer is valid)
        elseif ($Value -and $isValidPtr -and -not $Release) {
            $null = $Global:ntdll::RtlFreeUnicodeString($unicodeStringPtr)
            $Global:ntdll::RtlZeroMemory($unicodeStringPtr, [UIntPtr]::new(16))
            $returnValue = $Global:ntdll::RtlCreateUnicodeString($unicodeStringPtr, $Value)
           
            # Check if the lowest byte is 1 (indicating success as per the C code's CONCAT71)
            if (($returnValue -band 0xFF) -ne 1) {
                    throw "Failed to create Unicode string for '$Value'. NTSTATUS return value: 0x$hexReturnValue"
            }
            return
        }
    
        # Case 3: Pointer + Release ג†’ cleanup the string (if pointer is valid)
        elseif (-not $Value -and $isValidPtr -and $Release) {
            $null = $Global:ntdll::RtlFreeUnicodeString($unicodeStringPtr)
            New-IntPtr -hHandle $unicodeStringPtr -Release
            return
        }
    
        # Invalid combinations (no valid operation matched)
        else {
            throw "Invalid parameter combination. You must provide one of the following:
            1) -Value to create a new string,
            2) -Value and -unicodeStringPtr to reuse the pointer,
            3) -unicodeStringPtr and -Release to free the string."
        }
    }
    
    # work - job Here.
    $Global:ntdll = Init-NTDLL
    $Global:kernel32 = Init-KERNEL32
    $Global:PebPtr = $Global:ntdll::RtlGetCurrentPeb()
    $global:LoadedModules = Get-LoadedModules -SortType Memory |
        Select-Object BaseAddress, ModuleName, LoadAsData
    
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  4. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    #4 Dark Vador, Jul 7, 2025
    Last edited: Jul 7, 2025
    (OP)
    c style warper, take whole interface / structs, and cast them
    or like i see abbodi1406 did, build dummy function etc etc
    this one not, it use delegate, and fucntion index, and pointer's,
    you don't write any code
    and you don't have consider count from 0 + 3 Function + 4 because ..
    nope.!

    upload_2025-7-7_17-50-5.png

    and i also saved you from write -->
    no params, its ok. still work.

    upload_2025-7-7_17-54-32.png

    No need to think - dispatch or not ?

    upload_2025-7-7_18-0-11.png
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  5. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    #5 Dark Vador, Jul 7, 2025
    Last edited: Jul 7, 2025
    (OP)
    Another Example.
    show windows Install key UI

    * From CLIC.H Corp'
    https://github.com/asdcorp/clic/blob/principalis/clic.c

    Code:
     GUID guidEditionUpgradeManager = {
            0x17CCA47D, 0xDAE5, 0x4E4A,
            {0xAC, 0x42, 0xCC, 0x54, 0xE2, 0x8F, 0x33, 0x4A}
        };
        GUID guidIEditionUpgradeManager = {
            0xF2DCB80D, 0x0670, 0x44BC,
            {0x90, 0x02, 0xCD, 0x18, 0x68, 0x87, 0x30, 0xAF}
        };
    
    typedef struct _tagIEditionUpgradeManagerVtbl {
        HRESULT (WINAPI *QueryInterface)(
            VOID *pThis,
            const IID *riid,
            void **ppvObject
        );
    
        ULONG (WINAPI *AddRef)(
            VOID *pThis
        );
    
        ULONG (WINAPI *Release)(
            VOID *pThis
        );
    
        HRESULT (WINAPI *InitializeWindow)(
            VOID *pThis,
            OLE_HANDLE parentWindow
        );
    
        HRESULT (WINAPI *UpdateOperatingSystem)(
            VOID *pThis,
            LPWSTR lpwszContentId,
            DWORD dwAsync
        );
    
        HRESULT (WINAPI *ShowProductKeyUI)(
            VOID *pThis,
            DWORD dwAsync
        );
    
        HRESULT (WINAPI *UpdateOperatingSystemWithParams)(
            VOID *pThis,
            LPWSTR lpwszPKey,
            BOOL bReboot,
            BOOL bShowUI,
            BOOL bShowPrompt,
            BOOL bCommandLine,
            DWORD dwAsync
        );
    
        HRESULT (WINAPI *AcquireModernLicenseForWindows)(
            VOID *pThis,
            DWORD dwAsync,
            DWORD *pdwReturnCode
        );
    
        HRESULT (WINAPI *AcquireModernLicenseWithPreviousId)(
            VOID *pThis,
            LPWSTR lpwszPreviousId,
            DWORD *pdwReturnCode
        );
    } IEditionUpgradeManagerVtbl;
    
    End result's

    Code:
    $interfaceSpec = [PSCustomObject]@{
            Index  = 3
            Return = "int"
            Params = "uint dwAsync"
            Name   = "ShowProductKeyUI"
            CLSID  = "17CCA47D-DAE5-4E4A-AC42-CC54E28F334A"
            IID    = "F2DCB80D-0670-44BC-9002-CD18688730AF"
        }
    
        try {
            $comObject = Initialize-ComObject -InterfaceSpec $interfaceSpec
            $returnCode = 0
            $hr = $comObject | Invoke-ComObject -Params (
                @(0))
    
        } catch {
            Write-Warning "An error occurred: $($_.Exception.Message)"
            return $false
        } finally {
            $comObject | Release-ComObject
        }
    
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  6. abbodi1406

    abbodi1406 MDL KB0000001

    Feb 19, 2011
    17,663
    93,860
    340
    "[Marshal]" should be "[Runtime.InteropServices.Marshal]"
     
  7. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    i got a petent :D

    Code:
    using namespace System
    using namespace System.Collections.Generic
    using namespace System.Drawing
    using namespace System.IO
    using namespace System.IO.Compression
    using namespace System.Management.Automation
    using namespace System.Net
    using namespace System.Diagnostics
    using namespace System.Numerics
    using namespace System.Reflection
    using namespace System.Reflection.Emit
    using namespace System.Runtime.InteropServices
    using namespace System.Security.AccessControl
    using namespace System.Security.Principal
    using namespace System.ServiceProcess
    using namespace System.Text
    using namespace System.Text.RegularExpressions
    using namespace System.Threading
    using namespace System.Windows.Forms
    
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  8. abbodi1406

    abbodi1406 MDL KB0000001

    Feb 19, 2011
    17,663
    93,860
    340
    That's the worst feature in PS 5+, powershell should not be raw C# :p
     
  9. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    #9 Dark Vador, Jul 7, 2025
    Last edited: Jul 7, 2025
    (OP)
    i got a rule, use Ps1 - only ps1 code, until i arrived to com,
    it almost impossible, dont have delegate in PS1.
    so, i have to break my rule this time
    so, basiclyl most of code does manages .net,
    only 2 call's for delegate, are not. 90% it good than nothing. :D

    and, ps1 and com, making me hard life,
    hard to cast object, fail, or build dummy class's
    so, no other option's left.

    for some classes' its does let you access
    for few internalse, like .. DCB00C01-570F-4A9B-8D69-199FDBA5723B
    it expose them or free. include some function.

    $comObj.ComObject.IsConnected
    $comObj.ComObject.IsConnectedToInternet


    upload_2025-7-7_23-46-8.png
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  10. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    #10 Dark Vador, Jul 25, 2025
    Last edited: Jul 25, 2025
    (OP)
    Update code, add support for Un manged call & Com object's
    i put enough Com & Api invoke sample's for you to use
    delegate option is great flexible option, instead Import Dll, using Add Type
    Code:
    Clear-host
    write-host
    
    $Global:ntdll = Init-NTDLL
    $Global:kernel32 = Init-KERNEL32
    $Global:PebPtr = $Global:ntdll::RtlGetCurrentPeb()
    
    Use-ComInterface `
        -CLSID "17CCA47D-DAE5-4E4A-AC42-CC54E28F334A" `
        -IID "f2dcb80d-0670-44bc-9002-cd18688730af" `
        -Index 3 `
        -Name "ShowProductKeyUI" `
        -Return "void"
    
    Invoke-UnmanagedMethod `
        -Dll "kernel32.dll" `
        -Function "Beep" `
        -Return "bool" `
        -Params "uint dwFreq, uint dwDuration" `
        -Values @(750, 300)  # 750 Hz beep for 300 ms
    
    [intPtr]$ppEnumNetwork = [intPtr]::Zero
    Use-ComInterface `
        -CLSID "DCB00C01-570F-4A9B-8D69-199FDBA5723B" `
        -IID "DCB00000-570F-4A9B-8D69-199FDBA5723B" `
        -Index 1 `
        -Name "GetNetwork" `
        -Return "uint" `
        -Params 'system.UINT32 Flags, out INTPTR ppEnumNetwork' `
        -Values @(1, [ref]$ppEnumNetwork)
    if ($ppEnumNetwork -ne [IntPtr]::Zero) {
        $networkList = $ppEnumNetwork | Receive-ComObject
        foreach ($network in $networkList) {
            "Name: $($network.GetName()), IsConnected: $($network.IsConnected())"
        }
        $networkList | Release-ComObject
    }
    
    $buffer = New-IntPtr -Size 256
    $result = Invoke-UnmanagedMethod `
      -Dll "kernel32.dll" `
      -Function "GetComputerNameA" `
      -Return "bool" `
      -Params "IntPtr lpBuffer, ref uint lpnSize" `
      -Values @($buffer, [ref]256)
    if ($result) {
        $computerName = [Marshal]::PtrToStringAnsi($buffer)
        Write-Host "Computer Name: $computerName"
    } else {
        Write-Host "Failed to get computer name"
    }
    New-IntPtr -hHandle $buffer -Release
    
    # ZwQuerySystemInformation
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677
    # SYSTEM_PROCESS_INFORMATION structure
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/process.htm
    # ZwQuerySystemInformation
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677
    # SYSTEM_BASIC_INFORMATION structure
    # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi/system_basic_information.htm
    # Step 1: Get required buffer size
    $ReturnLength = 0
    $dllResult = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(0, [IntPtr]::Zero, 0, [ref]$ReturnLength)
    # Allocate buffer (add some extra room just in case)
    $infoBuffer = New-IntPtr -Size $ReturnLength
    # Step 2: Actual call with allocated buffer
    $result = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(0, $infoBuffer, $ReturnLength, [ref]$ReturnLength)
    if ($result -ne 0) {
        Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)"
        Parse-ErrorMessage -MessageId $result
        New-IntPtr -hHandle $infoBuffer -Release
        return
    }
    # Parse values from the structure
    $sysBasicInfo = [PSCustomObject]@{
        PageSize                     = [Marshal]::ReadInt32($infoBuffer,  0x08)
        NumberOfPhysicalPages        = [Marshal]::ReadInt32($infoBuffer,  0x0C)
        LowestPhysicalPageNumber     = [Marshal]::ReadInt32($infoBuffer,  0x10)
        HighestPhysicalPageNumber    = [Marshal]::ReadInt32($infoBuffer,  0x14)
        AllocationGranularity        = [Marshal]::ReadInt32($infoBuffer,  0x18)
        MinimumUserModeAddress       = [Marshal]::ReadIntPtr($infoBuffer, 0x20)
        MaximumUserModeAddress       = [Marshal]::ReadIntPtr($infoBuffer, 0x28)
        ActiveProcessorsAffinityMask = [Marshal]::ReadIntPtr($infoBuffer, 0x30)
        NumberOfProcessors           = [Marshal]::ReadByte($infoBuffer,   0x38)
    }
    # Step 1: Get required buffer size
    $ReturnLength = 0
    $dllResult = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(5, [IntPtr]::Zero, 0, [ref]$ReturnLength)
    # Allocate buffer (add some extra room just in case)
    $ReturnLength += 200
    $procBuffer = New-IntPtr -Size $ReturnLength
    # Step 2: Actual call with allocated buffer
    $result = Invoke-UnmanagedMethod `
      -Dll "ntdll.dll" `
      -Function "ZwQuerySystemInformation" `
      -Return "uint32" `
      -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" `
      -Values @(5, $procBuffer, $ReturnLength, [ref]$ReturnLength)
    if ($result -ne 0) {
        Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)"
        Parse-ErrorMessage -MessageId $result
        New-IntPtr -hHandle $procBuffer -Release
        return
    }
    $offset = 0
    $processList = @()
    while ($true) {
        try {
            $entryPtr = [IntPtr]::Add($procBuffer, $offset)
            $nextOffset = [Marshal]::ReadInt32($entryPtr, 0x00)
            $namePtr = [Marshal]::ReadIntPtr($entryPtr, 0x38 + [IntPtr]::Size)
            $processName = if ($namePtr -ne [IntPtr]::Zero) {
                [Marshal]::PtrToStringUni($namePtr)
            } else {
                "[System]"
            }
            $procObj = [PSCustomObject]@{
                ProcessId       = [Marshal]::ReadIntPtr($entryPtr, 0x50)
                ProcessName     = $processName
                NumberOfThreads = [Marshal]::ReadInt32($entryPtr, 0x04)
            }
            $processList += $procObj
            if ($nextOffset -eq 0) { break }
            $offset += $nextOffset
        } catch {
            Write-Host "Parsing error at offset $offset. Stopping."
            break
        }
    }
    New-IntPtr -hHandle $infoBuffer -Release
    New-IntPtr -hHandle $procBuffer -Release
    $sysBasicInfo | Format-List
    $processList | Sort-Object ProcessName | Format-Table ProcessId, ProcessName, NumberOfThreads -AutoSize
    
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  11. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    Darki upload new version, GetProcAddress replaced with LdrGetProcedureAddressForCaller
    Code:
    LdrGetProcedureAddressForCaller
    @{
        Name       = "LdrGetProcedureAddressForCaller"
        Dll        = "ntdll.dll"
        ReturnType = [Int64]
        Parameters = [Type[]]@(
            [IntPtr],                  # [HMODULE] Module handle pointer
            [IntPtr],                  # [PSTRING] Pointer to STRING struct (pass IntPtr directly, NOT [ref])
            [int64],                   # [ULONG]   Ordinal / Flags (usually 0)
            [IntPtr].MakeByRefType(),  # [PVOID*]  Out pointer to procedure address (pass [ref])
            [byte],                    # [Flags]   0 or 1 (usually 0)
            [IntPtr]                   # [Caller]  Nullable caller address, pass [IntPtr]::Zero if none
        )
    }
    
    And, also, new helper's like Init-NativeString, Parse-NativeString, Free-NativeString,
    which help to build _STRING & _UNICODE_STRING using [Marshal]
    Code:
    https://learn.microsoft.com/en-us/windows/win32/api/Winternl/ns-winternl-string
    typedef struct _STRING {
      USHORT Length;
      USHORT MaximumLength;
      PCHAR  Buffer;
    } STRING;
    
    https://learn.microsoft.com/en-us/windows/win32/api/subauth/ns-subauth-unicode_string
    typedef struct _UNICODE_STRING {
      USHORT Length;
      USHORT MaximumLength;
      PWSTR  Buffer;
    } UNICODE_STRING, *PUNICODE_STRING;
    
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  12. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    Update Code [again] yehhh,
    Ldr-Load will return the Current loaded module handle *** if possible
    Code:
        if (-not $global:LoadedModules) {
            $global:LoadedModules = Get-LoadedModules -SortType Memory |
                Select-Object BaseAddress, ModuleName, LoadAsData
        }
    
        # Equivalent to: if ((dwFlags & 0x62) == 0)
        $IsNormalLoad = ($dwFlags.value__ -band 0x62) -eq 0
    
        if (!$ForceNew -and !$Log) {
            $dllObjList = $global:LoadedModules |
                Where-Object { $_.ModuleName -ieq $dll }
    
            if ($dllObjList) {
                if ($IsNormalLoad) {
                    $dllObj = $dllObjList | Where-Object { -not $_.LoadAsData } | Select-Object -Last 1 -ExpandProperty BaseAddress
                } else {
                    $dllObj = $dllObjList | Where-Object { $_.LoadAsData } | Select-Object -Last 1 -ExpandProperty BaseAddress
                }
    
                if ($dllObj) {
                    Write-Warning "Returning reusable module object for $dll"
                    return $dllObj
                }
            }
        }
    
    and, here, just call ldr-Load, who will handle everything
    Code:
    function Initialize-ApiObject {
        param (
            [Parameter(Mandatory=$true, ValueFromPipeline)]
            [PSCustomObject]$ApiSpec
        )
        
        $hModule = [IntPtr]::Zero
        $BaseAddress = Ldr-LoadDll -dwFlags SEARCH_SYS32 -dll $ApiSpec.Dll
    
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  13. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    #13 Dark Vador, Jul 28, 2025 at 06:35
    Last edited: Jul 28, 2025 at 07:19
    (OP)
    So, i've upload new version.
    Base Version + Extended version
    Extended version, will allow you to do this
    Code:
    Write-Host
    $length = [Uint32]1
    $Ptr    = [IntPtr]::Zero
    $lastErr = Invoke-UnmanagedMethod `
        -Dll "ntdll.dll" `
        -Function "NtEnumerateBootEntries" `
        -Return "Int64" `
        -Params "IntPtr Ptr, ref uint length" `
        -Values @($Ptr, [ref]$length)
    $ntStatus = Parse-ErrorMessage `
        -MessageId $lastErr
    Write-Host "NtEnumerateBootEntries return Status -> $ntStatus"
    
    Extended version,
    Support new feature to expend TokenPrivileges for current process,
    so, you can load NTDLL Api, that need extra Privileges
    Added this section in this version
    Code:
    # Get Minimal Privileges To Load Some NtDll function
    $ProcId = [Process]::GetCurrentProcess() |select -ExpandProperty ID
    Adjust-TokenPrivileges `
        -ProcessId $ProcId `
        -Privilege @("SeDebugPrivilege","SeImpersonatePrivilege","SeIncreaseQuotaPrivilege","SeAssignPrimaryTokenPrivilege", "SeSystemEnvironmentPrivilege") `
        -Log -SysCall
    
    i did able to read some info of boot entiries,
    but didnt get same result *as* bcdedit /enum
    but it was a good experince

    what it return ?
    https://github.com/winsiderss/phnt/blob/master/ntexapi.h
    https://processhacker.sourceforge.io/doc/ntexapi_8h_source.html
    Code:
    /**
     * The NtEnumerateBootEntries routine retrieves information about all boot entries in the system boot configuration.
     *
     * @param Buffer A pointer to a buffer that receives the boot entries information.
     * @param BufferLength A pointer to a variable that specifies the size of the buffer. On return, it contains the size of the data returned.
     * @return NTSTATUS Successful or errant status.
     */
    NTSYSCALLAPI
    NTSTATUS
    NTAPI
    NtEnumerateBootEntries(
        _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer,
        _Inout_ PULONG BufferLength
        );
    
    // private
    typedef struct _BOOT_ENTRY_LIST
    {
        ULONG NextEntryOffset;
        BOOT_ENTRY BootEntry;
    } BOOT_ENTRY_LIST, *PBOOT_ENTRY_LIST;
    
    typedef struct _BOOT_ENTRY
    {
        ULONG Version;
        ULONG Length;
        ULONG Id;
        ULONG Attributes;
        ULONG FriendlyNameOffset;
        ULONG BootFilePathOffset;
        ULONG OsOptionsLength;
        _Field_size_bytes_(OsOptionsLength) UCHAR OsOptions[1];
    } BOOT_ENTRY, *PBOOT_ENTRY;
    
    // private
    typedef struct _FILE_PATH
    {
        ULONG Version;
        ULONG Length;
        ULONG Type;
        _Field_size_bytes_(Length) UCHAR FilePath[1];
    } FILE_PATH, *PFILE_PATH;
    
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  14. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    #14 Dark Vador, Jul 28, 2025 at 21:59
    Last edited: Jul 28, 2025 at 22:08
    (OP)
    Did upload v4 version of base & extended
    Some load improvements
    It’s make api calls very easy
    Better than reflections or add type
    I use to do dynamic loading in c$
    But didn’t see such in ps1.
    maybe ms should make one

    also update new-intptr
    To allow set first 4 bytes with value
    Of size or different Value
    Which is very useful
    Some structures req to write int size in 0x0
    So very useful

    and extended version
    Add real use sample for privileges must api calls
    Read boot entires, which is good option
    To demonstrate run api that req more than usually privileges
    Did happen to read something not all data
    It’s for demonstration only
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  15. Dark Vador

    Dark Vador X Æ A-12

    Feb 2, 2011
    4,695
    6,933
    150
    #15 Dark Vador, Aug 3, 2025 at 16:29
    Last edited: Aug 3, 2025 at 16:43
    (OP)
    Update script,
    New IntPtr support extra Option
    Code:
    function New-IntPtr {
        param(
            [Parameter(Mandatory=$false)]
            [int]$Size,
    
            [Parameter(Mandatory=$false)]
            [int]$InitialValue = 0,
    
            [Parameter(Mandatory=$false)]
            [IntPtr]$hHandle,
    
            [switch]$MakeRefType,
            [switch]$WriteSizeAtZero,
            [switch]$Release
        )
    
    Also add IsValid-IntPtr & Free-IntPtr, Support:
    Code:
    "HGlobal", "Handle", "NtHandle", "ServiceHandle", "Heap", "Auto"
    
    Also, add Function To run Process As System,
    thought it can be useful.
    ~~~~~~~~~~~~~
    MakeRefType
    , it seems Managed code can't handle well ~IntPtr->MakeRef,
    so, i create this option. real life example, that it actually matter.
    otherwise, createProcess api fail !
    Spend lot of time, Manual & Advice with MR GPT, why it cause trouble,
    and found out, that was the rare case, IntPtr->Ref, not work here!
    so, you can't trust, that it will work every time.!
    Code:
    # Allocate unmanaged memory for the handle
    $handlePtr = New-IntPtr -hHandle $tHandle -MakeRefType
    $ret = $Global:kernel32::UpdateProcThreadAttribute(
        $lpAttributeList, 0, 0x00020000, $handlePtr, [IntPtr]::Size, 0, 0)
    
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...