Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

C++ and PowerShell

Aug
2,799
144
I am naive when it comes to C++,
but CoPilot helped me in creating simple C++ code,
that demonstrates executing a PowerShell Command,
which I compiled,
and successfully executed.
C++:
#using <mscorlib.dll>
#using <System.Management.Automation.dll>

#include <iostream>
#include <string>
#include <msclr\auto_handle.h>

using namespace System;
using namespace System::Management::Automation;
using namespace System::Collections::ObjectModel;

void ExecutePowerShellCommand(String^ script) {
    // Create a PowerShell instance
    PowerShell^ ps = PowerShell::Create();
    ps->AddScript(script);

    // Execute the script and retrieve results

    Collection<PSObject^>^ results = ps->Invoke();

    // Output results
    for each (PSObject^ result in results) {
        Console::WriteLine(result->ToString());
    }
}

int main() {
    // PowerShell script to execute
    String^ script = "Get-Date";

    // Call the function to execute the script
    ExecutePowerShellCommand(script);

    return 0;
}

Compiled;
Bash:
R:\>#cl /clr test.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.42.34438
for Microsoft (R) .NET Framework version 4.08.9290.0
Copyright (C) Microsoft Corporation.  All rights reserved.

test.cpp
Microsoft (R) Incremental Linker Version 14.42.34438.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj[/COLOR]

Results of execution;
Bash:
2025-03-16 12:35:56 PM[/COLOR]

If I remember correctly,
TCC cannot be compiled using the /clr switch,
the /clr switch meaning "Enables applications and components to use features from the common language runtime (CLR) and enables C++/CLI compilation."

Keeping that in mind,
can TCC use a plugin that was compiled using the /clr switch?

If so,
would this be the start of a solution,
to allow C++ / Powershell integration with TCC?

Joe
 
Last edited:
I modified the VTFIX plugin (discussed elsewhere) by (1) changing the entry point from MyEntry to DllMain, (2) changing the runtime library to multithreaded DLL (vcruntime140.dll), and (3) adding the /clr option. It build, loaded, and its only export, ENABLEVT, ran with no complaints.

I'll try putting your code in it (maybe not right away).
 
I don't have system.management.automation and I don't know how to get it.
 
Using Visual Studio:

  • Open your project in Visual Studio.
  • Go to the "Tools" menu and select "NuGet Package Manager" > "Manage NuGet Packages for Solution."
  • Search for System.Management.Automation in the "Browse" tab.
  • Select the desired version (e.g., 7.5.0) and click "Install."

  • 1742223198407.webp

Joe
 
Code:
1>P:\vtfix\vtfix.cpp(14,2): error C2065: 'PowerShell': undeclared identifier
1>P:\vtfix\vtfix.cpp(14,14): error C2065: 'ps': undeclared identifier
1>P:\vtfix\vtfix.cpp(14,19): error C2653: 'PowerShell': is not a class or namespace name
1>P:\vtfix\vtfix.cpp(14,31): error C3861: 'Create': identifier not found
1>P:\vtfix\vtfix.cpp(15,2): error C2065: 'ps': undeclared identifier
1>P:\vtfix\vtfix.cpp(18,2): error C2065: 'Collection': undeclared identifier
1>P:\vtfix\vtfix.cpp(18,13): error C2065: 'PSObject': undeclared identifier

(and so on). Did you fail to post an include?
 
What is your LIBPATH set to?

This is mine;
Code:
set libpath=C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35

No includes required.
No project (.vcxproj) required.
No solution (.sln) required.

From the command line,
Code:
set libpath=C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35
cl /clr test.cpp

Joe
 
I don't do command line. My lib dirs are

Code:
C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35;$(WindowsSDK_LibraryPath_x64)

This file exists:

Code:
C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35\System.Management.Automation.dll

And I get this.

Code:
error C1107: could not find assembly 'System.Management.Automation.dll': please specify the assembly search path using /AI or by setting the LIBPATH environment variable
 
I don't do GUI, but here's the equivalent to the /AI command line switch,
Additional #using Directories;
1742232542993.webp


In Additional #using Directories, place the missing libpath
Code:
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35

Joe
 
Make sure that you have enabled Common Language Runtime Support.

I have mine set to .NET Framework Runtime Support (/clr)
1742233195478.webp


Ctrl-Shift-B builds the solution,
creating the .EXE, which runs,
calling a PowerShell command, and outputs the date.

Joe
 
I'm trying to build a plugin DLL and I'm tired of messing with it. Here's PSCOMMAND.CPP for you to play with. You'll have to finish the exported command PSCOMMAND and probably revise ExecutePowerShellCommand() accordingly. And you'll have to include TakeCmd.h and a path to TakeCmdx64.lib because you'll no doubt want to use Printf() (and probably other TakeCmd exports).
 

Attachments

Thanks Vince,
but I decided to start from scratch.

Code:
E:\...\Release>plugin /l plugin & plugin plugin & echo %_psdate & plugin /u plugin
Plugin:         _psdate
2025-03-17 8:05:07 PM
ECHO is OFF

This is just proof that a PowerShell command can be called via a TCC Plugin.
C++:
#define UNICODE 1
#define _UNICODE 1

#define _ATL_ALLOW_CHAR_UNSIGNED 1

#include <stdio.h>
#include <tchar.h>

#include <Windows.h>

#include "PlugIn.h"
#include "TakeCmd.h"

#using <mscorlib.dll>
#using <System.Management.Automation.dll>

#include <iostream>
#include <string>
#include <msclr\auto_handle.h>

using namespace System;
using namespace System::Management::Automation;
using namespace System::Collections::ObjectModel;

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  dwReason, LPVOID lpReserved )
{
    UNREFERENCED_PARAMETER(lpReserved);
    UNREFERENCED_PARAMETER(hModule);
    switch( (int)dwReason )    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
        default:
            break;
    }
    return TRUE;
}


DLLExports LPPLUGININFO WINAPI GetPluginInfo( void )
{
    static PLUGININFO piInfo;

    piInfo.pszDll = L"Plugin";
    piInfo.pszAuthor = L"JP Software";
    piInfo.pszEmail = L"[email protected]";
    piInfo.pszWWW = L"https://jpsoft.com";
    piInfo.pszDescription = L"Plugin Demo";
    piInfo.pszFunctions = L"_psdate";
    piInfo.nMajor = 1;
    piInfo.nMinor = 0;
    piInfo.nBuild = 1;

    return &piInfo;
}


DLLExports BOOL WINAPI InitializePlugin( void )
{
    return 0;
}


DLLExports BOOL WINAPI ShutdownPlugin( BOOL bEndProcess )
{
    UNREFERENCED_PARAMETER(bEndProcess);
    return 0;
}


DLLExports INT WINAPI _psdate(LPTSTR lpszSrtring)
{
    if (lpszSrtring == nullptr)
        return 1;
    // Get the current date and time
  
    // Create a PowerShell instance
    PowerShell^ ps = PowerShell::Create();
    ps->AddScript("Get-Date");

    // Execute the script and retrieve results
    Collection<PSObject^>^ results = ps->Invoke();

    // Output results
    for each (PSObject ^ result in results) {
        Console::WriteLine(result->ToString());
    }

    return 0;
}

As @rconn says that he'll need to either find another library that allows C++ / Powershell integration,
or remove the Powershell code from TCC,
with this proof that PowerShell can be called from a TCC Plugin,
this may be a solution.

I've tested this plugin on Windows 10 Pro,
and it would be appreciated if users could test this plugin on Windows 11.

I'm also wondering what will happen if the Windows system does not have the proper .NET Framework configuration.

Joe
 

Attachments

Last edited:
Just tried to load the plugin in a Windows Sandbox;
Code:
C:\Users\WDAGUtilityAccount\Desktop]plugin /l plugin.dll
TCC: (Sys) The specified module could not be found.
 "plugin.dll"

This may be related to a problem that I had over a year ago,
in which I was not able to run PSHELL from a Windows Sandbox,
or possibly not.

Yes, Powershell.exe works fine in Windows Sandbox.

Joe
 
I tries to build yours. How do I avoid these (same problem with mine). I really don't understand the x64.obj message.

Code:
1>D:\Projects2022\pscommand\pscommand.cpp(1,1): warning C4747: Calling managed 'DllMain': Managed code may not be run under loader lock, including the DLL entrypoint and calls reached from the DLL entrypoint
1>LINK : fatal error LNK1181: cannot open input file 'C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\lib\x64.obj'
 
Vince, attached are my VS2022 files.

Review and compare to your VS2022 settings.

Joe
 

Attachments

I'm still testing things that I can do with my C++/CLR plugin.

Just the one command, AddScript, so far;
Code:
U:\>plugin mypshell

MyPShell:       AddScript

Loading a C# file via PowerShell via the plugin, and executing a method;
Code:
U:\>addscript add-type -path %temp\MathClass.cs; [MathClass]::Add(652,4)
656

C#:
public class MathClass
{
   public static long Multiply(long x, long y)
   {
       return x * y;
   }

   public static long Add(long i, long j)
   {
       return i + j;
   }
}

I can even include the C# code directly into the C++/CLR plugin,
bypassing the need for PowerShell.

TCC does .NET!

Joe
 
Last edited:
I have this. They use your Invoke/for_each method.

Code:
v:\> plugin plugin

PLUGIN:         PSHELL          @PSHELL

v:\> pshell $PSVersionTable.PSVersion
5.1.19041.5607

v:\> echo %@pshell[$PSVersionTable.PSVersion]
5.1.19041.5607

I noticed a few things. First, for that particular command the output is different from the output in PowerShell. Here's PowerShell.

Code:
V:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      19041  5607

Second, errors and error messages are completely and silently ignored.

Third, the plugin DLL can't be (completely) unloaded. If I try plugin /u plugin TCC doesn't complain, the exports are no longer known to TCC, but the DLL remains in memory. I've read that that's a consequence of using CLR but I couldn't find much on the subject.

Code:
v:\> plugin plugin

PLUGIN:         PSHELL          @PSHELL

v:\> plugin /u plugin

v:\> plugin plugin

v:\> listdlls %_pid | grep -i plugin.dll
0x0000000083bf0000  0xf000    D:\tc34\PlugIns\PlugIn.dll
 
I have found many differences between my plugin and PSHELL,
as you have discovered with yours.

pshell /s "dir r:\" returns same results,
but in a different format with MyPShell dir r:\

Code:
R:\>pshell /s "20250331-20250321; get-date" 
10

March 21, 2025 1:07:00 PM



R:\>mypshell "20250331-20250321; get-date" 
20250331-20250321; get-date

R:\>mypshell 20250331-20250321; get-date 
10
2025-03-21 1:07:18 PM

TCC PShell does not like > nul in a .PS1 file;

Example code;

Code:
$path = resolve-path u:\add.dll > nul
$bytes = [System.IO.File]::ReadAllBytes($path) > nul
[System.Reflection.Assembly]::Load($bytes) > nul
[System.Reflection.Assembly]::LoadFrom($path) > nul

Code:
R:\>pshell r:\test.ps1 
PSHELL: System.Management.Automation.RuntimeException : Redirection to 'nul' failed: FileStream will not open Win32 devices such as disk partitions and tape drives. Avoid use of "\\.\" in the path.

MyPShell does not like > nul either,
but it displays nothing,
or hangs the TCC Process.

If I use
>nul from the TCC,
Code:
R:\>mypshell 10+1 > nul 
11

Code:
R:\>pshell 10+1 > nul 
PSHELL: Script file R:\10+1 does not exist.

I would also like to get MyPShell working with PowerShell 7,
but it's not an urgent thing for me at this time.

Joe
 
Hey @vefatica ,
Have you constructed your plugin to keep PowerShell persistent?

By persistent, I mean;
C#:
U:\>pshell /s $currentShares=4.1840

U:\>pshell /s $currentShares
4.184

I've been running all of my PowerShell .ps1 files using my C++/CLR plugin,
and they're running the same as when using the TCC internal PSHELL.

I have yet to figure out how to duplicate the TCC internal PSHELL in regard to being persistent.

My workaround for now is to use environment variables to store info.
C#:
U:\>MyPShell $env:CurrentShares=4.1840

U:\>MyPShell $env:CurrentShares
4.184

Joe
 
Going over my notes,
and reviewing posts on this forum,
it looks like a Runspace has to be created,
in order for PowerShell to be persistent.

Ref: v27 and Enter-PSHostProcess

Looking at this post...
Ref: Run a .PS1 file without PowerShell

...maybe a RunSpace can be created in the InitializePlugin() function of the plugin,
and a RunSpace can be removed in the ShutdownPlugin() function of the plugin.

Here are some links with reference material;
Ref: Creating Runspaces - PowerShell
Ref: Beginning Use of PowerShell Runspaces: Part 1 - Scripting Blog [archived]
Ref: How to implement Runspaces into a powershell script

I'll see what I can add to my plugin to accomplish this.

Hopefully, you can also use this info to add to your plugin.

Joe
 
Back
Top