I have a powershell script with PowerCLI commands to install and configure virtual machines.
These are the settings in the environment
Windows Server 2008 R2
ESX 4.1
PowerShell v2
PowerCLI 5.1
The script will be triggered from a user on a website. Following code start the script.
PathToScript is a UNC path
const string Path32BitPowerShell = #"C:\Windows\SysWOW64\Windowspowershell\v1.0\powershell.exe";
public static void Run32BitPowerShell(String PathToScript, Boolean WaitForExit, String Arguments = "")
{
Process PowerShellProcess = new Process();
PowerShellProcess.StartInfo.CreateNoWindow = true;
PowerShellProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
PowerShellProcess.StartInfo.FileName = Path32BitPowerShell;
PowerShellProcess.StartInfo.WorkingDirectory = #"C:\";
PowerShellProcess.StartInfo.Arguments = PathToScript + " " + Arguments;
PowerShellProcess.Start();
if (WaitForExit)
{
PowerShellProcess.WaitForExit();
}
}
The script has this two global settings:
$ErrorActionPreference = "Stop"
$ConfirmPreference = "None"
The whole code is inside a try block with catch/finally
But the code never goes into the catch block. I know it because I wrote one time as first line in the catch block Stop-Computer -Force -Confirm:$false and the webserver was still running after 5 minutes after the script was finished.
The code stops on the command Invoke-VMScript:
Invoke-VMScript -VM $VM -ScriptType Bat -ScriptText "powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -Command Set-ExecutionPolicy Unrestricted -Scope CurrentUser" -GuestUser "***" -GuestPassword "****"
The exit code from the C# process is 1
Important:
The script runs perfectly, wenn I start it interactive (from the ISE) !
The issue only occurs when the webserver starts it (non-interactive).
Someone has any idea, where the problem could be ?
/Update
It works also directly from the powershell command line, but also interactive
Maybe try
$Global:ErrorActionPreference = 'Stop'
$Global:ConfirmPreference = 'None'
Worked for me when I had very similar problem.
Related
I'm calling a self-elevating powershell script from C# code. The Script resets DNS Settings.
The script works fine when called from unelevated powershell, but takes no effect when called from C# code with no exceptions thrown.
My Execution policy is temporarily set on unrestricted and I'm running Visual Studio as Admin.
Does anyone know what's wrong?
The C#:
class Program
{
static void Main(string[] args)
{
var pathToScript = #"C:\Temp\test.ps1";
Execute(pathToScript);
Console.ReadKey();
}
public static void Execute(string command)
{
using (var ps = PowerShell.Create())
{
var results = ps.AddScript(command).Invoke();
foreach (var result in results)
{
Console.WriteLine(result.ToString());
}
}
}
}
The script:
# Get the ID and security principal of the current user account
$myWindowsID = [System.Security.Principal.WindowsIdentity]::GetCurrent();
$myWindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal($myWindowsID);
# Get the security principal for the administrator role
$adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator;
# Check to see if we are currently running as an administrator
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running as an administrator, so change the title and background colour to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)";
$Host.UI.RawUI.BackgroundColor = "DarkBlue";
Clear-Host;
}
else {
# We are not running as an administrator, so relaunch as administrator
# Create a new process object that starts PowerShell
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "PowerShell";
# Specify the current script path and name as a parameter with added scope and support for scripts with spaces in it's path
$newProcess.Arguments = "& '" + $script:MyInvocation.MyCommand.Path + "'"
# Indicate that the process should be elevated
$newProcess.Verb = "runas";
# Start the new process
[System.Diagnostics.Process]::Start($newProcess);
# Exit from the current, unelevated, process
Exit;
}
# Run your code that needs to be elevated here...
Set-DnsClientServerAddress -InterfaceIndex 9 -ResetServerAddresses
As you've just determined yourself, the primary problem was that script execution was disabled on your system, necessitating (at least) a process-level change of PowerShell's execution policy, as the following C# code demonstrates, which calls
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass before invoking the script file (*.ps1):
For an alternative approach that uses the initial session state to set the per-process execution policy, see this answer.
The approach below can in principle be used to persistently change the execution policy for the current user, namely by replacing .AddParameter("Scope", "Process") with .AddParameter("Scope", "CurrentUser")
Caveat: When using a PowerShell (Core) 7+ SDK, persistent changes to the local machine's policy (.AddParameter("Scope", "LocalMachine")) - which require running with elevation (as admin) - are seen by that SDK project only; see this answer for details.
Caveat: If the current user's / machine's execution policy is controlled by a GPO (Group Policy Object), it can NOT be overridden programmatically - neither per process, nor persistently (except via GPO changes).
class Program
{
static void Main(string[] args)
{
var pathToScript = #"C:\Temp\test.ps1";
Execute(pathToScript);
Console.ReadKey();
}
public static void Execute(string command)
{
using (var ps = PowerShell.Create())
{
// Make sure that script execution is enabled at least for
// the current process.
// For extra safety, you could try to save and restore
// the policy previously in effect after executing your script.
ps.AddCommand("Set-ExecutionPolicy")
.AddParameter("Scope", "Process")
.AddParameter("ExecutionPolicy", "Bypass")
.Invoke();
// Now invoke the script and print its success output.
// Note: Use .AddCommand() (rather than .AddScript()) even
// for script *files*.
// .AddScript() is meant for *strings
// containing PowerShell statements*.
var results = ps.AddCommand(command).Invoke();
foreach (var result in results)
{
Console.WriteLine(result.ToString());
}
// Also report non-terminating errors, if any.
foreach (var error in ps.Streams.Error)
{
Console.Error.WriteLine("ERROR: " + error.ToString());
}
}
}
}
Note that the code also reports any non-terminating errors that the script may have reported, via stderr (the standard error output stream).
Without the Set-ExecutionPolicy call, if the execution policy didn't permit (unsigned) script execution, PowerShell would report a non-terminating error via its error stream (.Streams.Error) rather than throw an exception.
If you had checked .Streams.Error to begin with, you would have discovered the specific cause of your problem sooner.
Therefore:
When using the PowerShell SDK, in addition to relying on / catching exceptions, you must examine .Streams.Error to determine if (at least formally less severe) errors occurred.
Potential issues with your PowerShell script:
You're not waiting for the elevated process to terminate before returning from your PowerShell script.
You're not capturing the elevated process' output, which you'd have to via the .RedirectStandardInput and .RedirectStandardError properties of the System.Diagnostics.ProcessStartInfo instance, and then make your script output the results.
See this answer for how to do that.
The following, streamlined version of your code addresses the first point, and invokes the powershell.exe CLI via -ExecutionPolicy Bypass too.
If you're using the Windows PowerShell SDK, this shouldn't be necessary (because the execution policy was already changed in the C# code), but it could be if you're using the PowerShell [Core] SDK, given that the two PowerShell editions have separate execution-policy settings.
# Check to see if we are currently running as an administrator
$isElevated = & { net session *>$null; $LASTEXITCODE -eq 0 }
if ($isElevated)
{
# We are running as an administrator, so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "DarkBlue"
Clear-Host
}
else {
# We are not running as an administrator, so relaunch as administrator
# Create a new process object that starts PowerShell
$psi = New-Object System.Diagnostics.ProcessStartInfo 'powershell.exe'
# Specify the current script path and name as a parameter with and support for scripts with spaces in its path
$psi.Arguments = '-ExecutionPolicy Bypass -File "{0}"' -f
$script:MyInvocation.MyCommand.Path
# Indicate that the process should be elevated.
$psi.Verb = 'RunAs'
# !! For .Verb to be honored, .UseShellExecute must be $true
# !! In .NET Framework, .UseShellExecute *defaults* to $true,
# !! but no longer in .NET Core.
$psi.UseShellExecute = $true
# Start the new process, wait for it to terminate, then
# exit from the current, unelevated process, passing the exit code through.
exit $(
try { ([System.Diagnostics.Process]::Start($psi).WaitForExit()) } catch { Throw }
)
}
# Run your code that needs to be elevated here...
Set-DnsClientServerAddress -InterfaceIndex 9 -ResetServerAddresses
Hi I'm trying to convert either a doc or docx to a pdf in a c# MVC application. I know I can do this using libreOffice. So I created a simple batch file to take 2 variables and then run them into the libreoffice 'soffice' headless to convert to pdf.
So that gave me this code.
echo on
SET var1=%2
IF "%var1:~-1%"=="\" SET var1=%var1:~0,-1%
cd "C:\Program Files\LibreOffice 5\program\"
soffice --headless --convert-to pdf %1 --outdir %var1%
Originally I thought the problem was within my MVC application and the way I called this batch script. But I commented (REM) the soffice and outputted out the command in the bash using the standard output.
var psi = new ProcessStartInfo("cmd.exe", "/k " + command);
//psi.CreateNoWindow = true;
psi.FileName = command;
psi.UseShellExecute = false;
psi.RedirectStandardError = true;
psi.RedirectStandardOutput = true;
psi.Arguments = string.Format("{0} {1}", fullPath2, tempPath);
var process = Process.Start(psi);
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
Trace.WriteLine(output);
Trace.WriteLine(error);
process.WaitForExit();
When I commented the soffice line - it hit the WaitForExit and worked no problems (ok with no pdf conversions, but the script exited).
If I don't do that it seems to execute the cmd and even the soffice commands because I can see them in the task manager - but obvisouly nothing happens.
Additionally the code above works when I did a c# command line program (I've hard coded the file/command lines in both instances). The executable also works when I run as the user that is running the app pool in my MVC application.
The bash file also works file 'standalone' no matter if me or my appPool user run it.
So what gives - why won't this run.
This is the code that comes out of that trace - so what the bash script does.
c:\windows\system32\inetsrv>echo on
c:\windows\system32\inetsrv>SET var1=C:\inetpub\xxxxxxxxx\Temp\
c:\windows\system32\inetsrv>IF "\" == "\" SET var1=C:\inetpub\xxxxxxxxx\Temp
c:\windows\system32\inetsrv>cd "C:\Program Files\LibreOffice 5\program\"
C:\Program Files\LibreOffice 5\program>soffice --headless --convert-to pdf C:\inetpub\xxxxxxxxx\Temp\636295920370843147.doc --outdir C:\inetpub\xxxxxxxxx\Temp
I've got a feeling that this has something to do with the amount of characters or something because the soffice does fireup (can see it in the task manager).
FYI there are no spaces or special characters anywhere.
Any ideas?
Update
This looks to be an issue with the wait command. So any help with that helpful, I'm starting to think perhaps this is an issue with c# and libreoffice 5 - I've seen examples that supposedly work with libreoffice 4.
I guess my challenge continues....
I have a C# (ASP Core) service running on Windows Server 2012 R2 that executes Newman test suites via the command line. The command the service executes works perfectly when run directly in the command prompt, but is not working from the service. To add insult to injury, the very same command DOES work from the service running locally on my dev machine (Windows 10 Pro). I am certain I am running the same command in all instances, as the service outputs the CLI's StandardOutput into a file, the contents of which I paste straight into the command prompt.
EDIT: The service is hosted on IIS.
The error I receive:
module.js:471
throw err;
^
Error: Cannot find module 'C:\Users\MyName\AppData\Roaming\npm\node_modules\newman\bin\newman.js'
at Function.Module._resolveFilename (module.js:469:15)
at Function.Module._load (module.js:417:25)
at Module.runMain (module.js:604:10)
at run (bootstrap_node.js:393:7)
at startup (bootstrap_node.js:150:9)
at bootstrap_node.js:508:3
The command I'm running (all paths are the same in every instance):
"C:\Program Files\NodeJS\node.exe" C:\Users\MyName\AppData\Roaming\npm\node_modules\newman\bin\newman.js run https://api.getpostman.com/collections/MyCollectionURI -r cli,junit --reporter-junit-export D:\TestHarnessServiceLogs\XML\{FilenameFromDatetime}.xml -e https://api.getpostman.com/environments/MyEnvironmentURI --disable-unicode
C# to build and run command:
//Build over multiple lines to make it vaguely readable, then string replace away the newlines so it runs as one command
string runTestCmd = $#"{_nodeExecutablePath}
{_newmanDotJsFile} run
{collectionPath}
-r cli,junit
--reporter-junit-export {_junitReportPathWithFilename}
-e {environmentPath}
--disable-unicode"
.Replace(Environment.NewLine, " ")
.Replace("\t", "");
File.WriteAllText(#"D:\TestHarnessServiceLogs\Command.txt", runTestCmd);
//Launch hidden CLI
using (Process p = new Process())
{
p.StartInfo.FileName = #"C:\Windows\System32\cmd.exe";
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.Arguments = _nodeVarsBatPath;
p.Start();
//Execute commands
using (StreamWriter sw = p.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
sw.WriteLine(runTestCmd);
}
}
//Parse the various outputs
output = p.StandardOutput.ReadToEnd();
error = p.StandardError.ReadToEnd();
exitCode = p.ExitCode.ToString(); //Always returns 0, think this is because it evaluates success of creating the process, not what happens inside it
File.WriteAllText(#"D:\TestHarnessServiceLogs\output.txt", output);
File.WriteAllText(#"D:\TestHarnessServiceLogs\error.txt", error);
File.WriteAllText(#"D:\TestHarnessServiceLogs\exitCode.txt", exitCode);
}
Newman is installed globally in both environments, and some relevant AppSettings below (names modified slightly for brevity):
"_newmanDotJsFile": "C:\\Users\\MyName\\AppData\\Roaming\\npm\\node_modules\\newman\\bin\\newman.js",
"_nodeVarsBatPath": "\"C:\\Program Files\\NodeJS\\nodevars.bat\"",
"_nodeExecutablePath": "\"C:\\Program Files\\NodeJS\\node.exe\"",
How can an identical command find the newman module and run fine from the CLI but not from the service?
EDIT: The user the service is running under couldn't access the file, having done that I now get the following (obviously permissions based) error instead, think I know where this is going...:
fs.js:994
binding.lstat(pathModule._makeLong(path), statValues);
^
Error: EPERM: operation not permitted, lstat 'C:\Users\MyName'
at Error (native)
at Object.fs.lstatSync (fs.js:994:11)
at Object.realpathSync (fs.js:1676:21)
at toRealPath (module.js:133:13)
at Function.Module._findPath (module.js:181:22)
at Function.Module._resolveFilename (module.js:467:25)
at Function.Module._load (module.js:417:25)
at Module.runMain (module.js:604:10)
at run (bootstrap_node.js:393:7)
at startup (bootstrap_node.js:150:9)
EDIT 2: Created a new user for the service to run under, installed newman for that user and gave it the right permissions (didn't seem clever to give Network Service access to my profile) - all is now working!
It sounds like the service doesn't run as your user? Perhaps that's why it doesn't find the file in that location you specified.
I am trying to run a power shell script from the c#.
When running the power shell script only, it runs successfully. But , while trying to run the same script from the c# . I get the error "The term 'New-CsOnlineSession' is not recognized as the name of a cmdlet"
Here is the code:
public static void GetLyncUsers(string userName, string password)
{
using (PowerShell powerShellInstance = PowerShell.Create())
{
var script = string.Format("$Username =\"{0}\"\n" +
"$Password =\"{1}\"\n" +
"$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force\n" +
"$cred = new-Object System.Management.Automation.PSCredential ($Username , $secpasswd)\n" +
"$CSSession = New-CsOnlineSession -Credential $cred\n" +
"Import-PSSession $CSSession -AllowClobber\n" +
"Get-CsOnlineUser", userName, password);
// use "AddScript" to add the contents of a script file to the end of the execution pipeline.
// use "AddCommand" to add individual commands/cmdlets to the end of the execution pipeline.
powerShellInstance.AddScript(script);
// use "AddParameter" to add a single parameter to the last command/script on the pipeline.
// invoke execution on the pipeline (collecting output)
Collection<PSObject> psOutput = powerShellInstance.Invoke();
// check the other output streams (for example, the error stream)
if (powerShellInstance.Streams.Error.Count > 0)
{
// I am getting this error
//The term 'New-CsOnlineSession' is not recognized as the name of a cmdlet
}
}
Is there anything i am missing? I am new to powershell in general.
Solution:
using (PowerShell powerShellInstance = PowerShell.Create())
{
// Import-Module lynconlineconnector
powershellInstance.Commands
.AddCommand("Import-Module")
.AddArgument("lynconlineconnector");
// rest of your code ....
Why?
When running an interactive session in powershell v3 and higher, the host traps CommandNotFound, and searches every module in all the known locations. If it finds the command, it automatically loads the module, and proceeds normally.
When running same script in C#, the CommandNotFound exception isn't trapped, and hence you get the error.
Related Question(s):
PowerShell - How to Import-Module in a Runspace
#PSTip Cmdlet Discovery and Module auto-loading
I ran into the same problem. You have to install the Lync/Skype For Business Online Connector as described on Technet
The setup program copies the Skype for Business Online Connector
module (and the New-CsOnlineSession cmdlet) to your local computer.
I have written an application that installs Windows Roles and Features using the Powershell API. It works just fine in Windows 2008 R2, but nothing happens in Windows 2012; the program just moves on as if everything happened just fine, but nothing is installed.
I've tried making the program .NET v4.5 (it was .NET v2.0), but that didn't help. I've been all over Google about this and I can't find a solution that works. In fact, most say to use the sort of implementation that works in Windows 2008 R2. Here is my code:
public bool runPowerShell(string command, string args)
{
mLogger myLogger = mLogger.instance; //How I log stuff in my application.
bool done = false; //default Return value.
const string path = #"C:\\XMPLogs\\Roles and Features"; //Where Powershell output will go.
//Make sure Powershell log directory is there.
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
//Start a new Powershell instance.
PowerShell powershell = PowerShell.Create();
System.Collections.ObjectModel.Collection<PSObject> output = new System.Collections.ObjectModel.Collection<PSObject>();
StringBuilder strBuilder = new StringBuilder(); //Used to examine results (for testing)
powershell.AddScript(#"Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted");
powershell.AddScript(#"Import-Module ServerManager");
//powershell.Invoke();
powershell.AddScript(command + " " + args);
try
{
output = powershell.Invoke();
// Construct a StringBuilder to examine the output of Invoke()
foreach (PSObject obj in output)
strBuilder.AppendLine(obj.ToString());
// Show the StringBuilder to see results (always empty!)
MessageBox.Show(strBuilder.ToString());
done = true;
}
catch (Exception ex)
{
string test = ex.ToString();
MessageBox.Show(test);
myLogger.output("ERRO", "PowerShell command " + command
+ "failed to run with arguments \"" + args + "\". Message: " + ex.ToString());
done = false;
}
powershell.Dispose();
return done;
}
I would call the method like this:
runPowerShell("add-windowsfeature", "-name FS-FileServer -logpath \"c:\\XMPLogs\\Roles and Features\\File Services.log\"");
The "output" object never has any data in it nor does the log file. So, I have no idea what is going on. I do know if I take the two parameters in the method call and enter them into a Powershell prompt manually, the install runs flawlessly.
Can anyone see what I'm doing wrong with this implementation on Windows Server 2012?
Thank you.
It's hard to know what is the real problem here, without more information. Perhaps you're not running your application as an administrator and therefore aren't allowed to add windows features?
However, PowerShell differs between terminating errors (which would block the execution and throw an exception, which should make your code enter the catch statement) and non-terminating errors (which are just written to the error stream and will not enter your catch statement).
You can read more about this if you run Get-Help Write-Error, Get-Help about_Throw and Get-Help about_Try_Catch_Finally.
I'm guessing your powershell command results in a non-terminating error. To find out whether a non terminating error has occured or not, you could check the powershell.HadErrors property and to get the error messages you can read the powershell.Streams.Error property.
This should probably help you in finding out what errors are occuring and hopefully help you solve your problem.