1

I'm trying to create a new local user in Windows by running a PowerShell script from a C# console application. I installed the necessary NuGet packages Microsoft.PowerShell.SDK (v7.5.2) and System.Management.Automation. I'm using System.Management.Automation.PowerShell.Create() in my C# code to run the script:

using System.Management.Automation;

using (var ps = PowerShell.Create())
{
    ps.AddScript(@"
        Import-Module Microsoft.PowerShell.LocalAccounts
        $password = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
        New-LocalUser -Name 'TestUser' -Password $password -FullName 'Test User' -Description 'Created from C#'
    ");
    var results = ps.Invoke();

    if (ps.HadErrors)
    {
        foreach (var error in ps.Streams.Error)
            Console.WriteLine("Error: " + error.ToString());
    }
}

Error when the script runs:

The term 'New-LocalUser' is not recognized as the name of a cmdlet, function, script file, or operable program...

  • The application is run as administrator via an application manifest with requireAdministrator.
  • The script explicitly runs Import-Module Microsoft.PowerShell.LocalAccounts.
  • The same PowerShell code works when run manually from an elevated PowerShell session.

Despite this, when run via PowerShell.Create() the New-LocalUser cmdlet is not recognized.

  • Does Microsoft.PowerShell.SDK not expose the full module set when running in this context?
  • Is PowerShell.Create() too sandboxed to access Microsoft.PowerShell.LocalAccounts?
  • Is there a better way to host PowerShell with full access to local account cmdlets?
10
  • 3
    I can't really repro this, can you please add your .csproj to this question ? Commented Jul 26 at 15:26
  • Adding to my prev. comment, this is a net9 project including Microsoft.PowerShell.SDK 7.5.2: i.imgur.com/j7ot6Kd.png doing ps.AddCommand("Get-LocalUser").AddCommand("Out-String") and not seeing any problem. Commented Jul 26 at 16:43
  • The cmdlet is not in the PSModulePath. Whatever account is being used the PSModulePath does have the directory where New-LocalUser is located. When you installed did you install for all users? Commented Jul 26 at 20:38
  • 1
    Are you actually running this on Windows (and not say in a Docker container or a VM)? Commented Jul 26 at 22:58
  • 1
    Davidew74: Note that you can help future readers by clearly signaling which answer, if any, solved your problem, namely by accepting it; in short: to accept an answer, click the large gray check mark (✔️) to the left of it. Sounds like @Itallmakescents' answer qualifies. Also, if you had included both errors that occur when trying to use the module in question from a 32-bit application - one for the inability to load the module and another for the follow-on error of not finding the command - you probably would have received an answer sooner. Commented Jul 31 at 16:01

2 Answers 2

1

According to the Microsoft.PowerShell.LocalAccounts module documentation:

This module is not available in 32-bit PowerShell on a 64-bit system.

If one attempts to run one's application as 32-bit on a 64-bit Windows operating system, then one receives the following error messages:

Error: The specified module 'Microsoft.PowerShell.LocalAccounts' was not loaded because no valid module file was found in any module directory.

Error: The term 'New-LocalUser' is not recognized as a name of a cmdlet, function, script file, or executable program.

Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

As others have stated, the code in the OP works if one runs one's application as 64-bit, when running on a 64-bit operating system. Add the following code before the code in the OP:

if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess)
{
    throw new NotSupportedException("This module is not available in 32-bit PowerShell on a 64-bit system. See https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.localaccounts for more information.");
}

While not related to the issue that you've describe in the OP, the code in the OP doesn't add the user to a group such as "Users", so the user won't be able to logon. It may be necessary to add the following code to the code in the OP:

Add-LocalGroupMember -Group 'Users' -Member 'TestUser'

The code below demonstrates an alternative to Add-Script. You'll notice that the code contains a check to ensure that if one is running the application on a 64-bit operating system that the application is running as 64-bit.

If you still receive an error message and your application is running as 64-bit when running on a 64-bit operating system, then verify that %windir%\System32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.LocalAccounts\1.0.0.0\Microsoft.PowerShell.LocalAccounts.dll exists.


The following shows how to use Nuget package Microsoft.PowerShell.SDK (v7.5.2) to add a local user to a computer. Some of the steps you mentioned that you already did, but I'll include them for future readers.

First, one needs to determine the .NET version supported by a particular version of Nuget package Microsoft.PowerShell.SDK (v7.5.2). Clicking on the previous URL, one sees that Microsoft.PowerShell.SDK verion 7.5.2 is supported in .NET 9.

Create a new Console App project (Framework: .NET 9) or Windows Forms App project (Framework: .NET 9)

Add an Application Manifest to your project

Note: This is used to prompt the user to execute the program as Administrator.

  • In VS menu, click Project
  • Select Add New Item...
  • Select Application Manifest File (Windows Only) (name: app.manifest)
  • Click Add

In app.manifest, replace

<requestedExecutionLevel level="asInvoker" uiAccess="false" />

with

<requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />

Note: Ensure that you've configured NuGet Package Manager to use PackageReference in Visual Studio (before adding NuGet package(s)):

  • In Visual Studio menu, click Tools
  • Select Options...
  • Expand NuGet Package Manager
  • Select General
  • Under Package Management, for Default package management format, select PackageReference.
  • Click OK

Download and install NuGet package: Microsoft.PowerShell.SDK (v7.5.2)

  • In Solution Explorer, right-click <project name> and select Manage NuGet Packages...

  • Click Browse

  • In the search box type: Microsof.PowerShell.SDK

  • Select Microsoft.PowerShell.SDK

  • Select desired version (ex: 7.5.2)

  • Click Install

    *Note: If a Preview Changes dialog box appears, click Apply. If a License Acceptance dialog box appears, choose I Accept or I Decline. If you chose I Accept, continue below. If you chose I Decline, then you can stop reading this post since the information below assumes that the Nuget package was successfully installed (ie: that one has accepted the license agreement).


Add the following using directives:

  • using System.Management.Automation;

  • using System.Management.Automation.Runspaces;

private static string PSAddUser(string username, string password, string fullName, string description)
{
    string result = string.Empty;

    if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess)
    {
        throw new NotSupportedException("This module is not available in 32-bit PowerShell on a 64-bit system. See https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.localaccounts for more information.");
    }

    InitialSessionState iss = InitialSessionState.CreateDefault2();

    //set execution policy
    iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.RemoteSigned; 

    //import module: Microsoft.PowerShell.LocalAccounts
    //string localUserModuleFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "WindowsPowerShell", "v1.0", "Modules", "Microsoft.PowerShell.LocalAccounts", "1.0.0.0");
    //iss.ImportPSModulesFromPath(localUserModuleFolder);
    iss.ImportPSModule("Microsoft.PowerShell.LocalAccounts"); //this may be unnecessary

    using (Runspace runspace = RunspaceFactory.CreateRunspace(iss))
    {
        //open
        runspace.Open();

        using (PowerShell ps = PowerShell.Create(runspace))
        {
            //it's not necessary to specify 'username', since only the password is used
            SecureString secureStringPass = new NetworkCredential("", password).SecurePassword;
            //SecureString secureStringPass = new NetworkCredential(username, password).SecurePassword;

            ps.AddCommand("New-LocalUser")
                .AddParameter("Name", username)
                .AddParameter("FullName", fullName)
                .AddParameter("Password", secureStringPass)
                .AddParameter("Description", description);

            //execute command
            System.Collections.ObjectModel.Collection<PSObject> outputCollectionNewLocalUser = ps.Invoke();

            //check if any errors/exceptions exist
            if (ps.HadErrors)
            {
                //read errors
                foreach (ErrorRecord error in ps.Streams.Error)
                {
                    //ToDo: add desired code (if desired, throw exception)
                    result += $"Error: {error.ToString()}{Environment.NewLine}";
                }
            }

            //read output (ie: StandardOutput)
            foreach (PSObject outputItem in outputCollectionNewLocalUser)
            {
                //create reference
                string? data = outputItem.BaseObject?.ToString();

                if (!String.IsNullOrEmpty(data))
                    result += $"{data}{Environment.NewLine}";
            }

            ps.Commands.Clear(); //clear previous command(s)
            ps.Streams.Error.Clear(); //clear any previous error(s)
            
            //add to 'Users' group (or any other desired group)
            ps.AddCommand("Add-LocalGroupMember")
                 .AddParameter("Group", "Users")
                 .AddParameter("Member", username);

            //execute command
            System.Collections.ObjectModel.Collection<PSObject> outputCollectionAddLocalGroupMember = ps.Invoke();

            //Add-LocalGroupMember doesn't produce any output unless there's an error
            //so there's no need to read outputCollection
            //check if any errors/exceptions exist
            if (ps.HadErrors)
            {
                //read errors
                foreach (ErrorRecord error in ps.Streams.Error)
                {
                    //ToDo: add desired code (if desired, throw exception)
                    result += $"Error: {error.ToString()}{Environment.NewLine}";
                }
            }
        }
    }

    return result;
}

Usage:

//get assembly (ie: application) name
string? applicationName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;

string description = string.Empty;

if (!String.IsNullOrEmpty(applicationName))
    description = $"Created by {applicationName}";

String result = PSAddUser("TestUser, "Password123!", "Test User", description);
Debug.WriteLine(result);

Resources:

Sign up to request clarification or add additional context in comments.

Comments

0

After suggestions in the comment thread of my answer I have updated my answer.

The approach below is an alternative way of running the Powershell script in the question.

You can run the Powershell script via starting it via an external Powershell process with arguments. Note that this is not efficient as it will start a subprocess and is limited also due to Powershell just accepting text to the script to run:

void Main()
{
    NewUser();
}

void RunPowershellscript(string script)
{
    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = "powershell.exe",
            Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"{script}\"",
            Verb = "runas",
            UseShellExecute = true
        };
        Process.Start(psi);
    }
    catch (Exception err)
    {
        Console.WriteLine($"Running powershell script failed. Reason: {err}");
    }
}

void NewUser()
{
     string script =
     @"
        Import-Module Microsoft.PowerShell.LocalAccounts
        $password = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
        New-LocalUser -Name 'TestUser' -Password $password -FullName 'Test User' -Description 'Created from C#'
    ";
    RunPowershellscript(script);
}

By using the Verb set to "runas", we will prompt the user running the C# program to enter credentials and creating a local user will usually demand that you use an administrator.

If you then forexample run CMD and enter:

lusrmgr.msc

you can check that the new local user was indeed created.

Created new user

As a note, I run your Powershell script from Linqpad and added the System.Management.Automation Nuget package. Note that i tested this with Linqpad 8, so I use .NET 8, but I think this approach should also work with .NET 9, I did not make a C# console project here with .NET 9 set as Target Framework. Since .NET 9 uses Powershell 7.5, you can try out the approach above.

3 Comments

Thanks for updating; I've removed my comments here, since you incorporated my feedback into the answer; one quibble: the text-only limitation applies to both providing input and receiving output.
Invoking a powershell.exe process isn't required as the screenshot in my comment stackoverflow.com/questions/79715794/… already demonstrates. OP's issue cannot be reproduced as is and there is a need for more details, i.e. his csproj.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.