Passing multiline script to PowerShell from another PowerShell? - c#

I'm new to Powershell and I found it difficult to tell my first powershell commandline (powershell_1) to : start a new powershell commandline (powershell_2) & let it (the powershell_2) execute my multiline script (written under powershell).
My script is :
$wshell = New-Object -ComObject Wscript.Shell -ErrorAction Stop;
$wshell.Popup("Are you looking at me?",0,"Hey!",48+4);
The code I'am using to launch the first powershell commandline and set things to work as intended
Process powershell_1 = new Process();
powershell_1.StartInfo.FileName = "powershell";
string argPowershell_2 = "help";
powershell_1.StartInfo.Arguments = "Start-Sleep -Milliseconds 500; start powershell -argumentlist \" "+ argPowershell_2 + " \"";
MessageBox.Show(powershell_1.StartInfo.Arguments);
powershell_1.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
powershell_1.Start();
In the example above, I could tell the 2nd commandline to execute help in the second one. I'm looking for a good way to escape quotes problems and tell it in a correct manner how to execute myscript instead.
EDIT
Ps : I'm not trying to pass a path of a someScript.ps to the second powershell commandline. I want to pass it literally, in multiline format.

I don't know if is the right manner, but you can do the same with the code below
$argPowershell_2 = "help";
Start-Process -Wait powershell -ArgumentList "Start-Sleep -Milliseconds 500; start powershell -argumentlist $argPowershell_2"

Solution
The argument to pass to my powershell_1 process in my case could be :
start powershell -argumentlist "-noexit","start-job -scriptblock { help; start-sleep 1;}
Using this line of code, and by tracking the background job in the new powershell process I got the following result :
If you want, inside the scriptblock to run something like (declaring variables, defining/calling custom functions, ... etc) :
$a = new-object System.net.webclient;
$a.downloadfile('https://www.stackoverflow.com','d:\file');
you need to escape the dollar sign ($) as well as any other double quote (") using the ` char.
start powershell -argumentlist "-noexit","start-job -scriptblock { `$a = new-object system.net.webclient; `$a.downloadfile('https://www.stackoverflow.com','d:\file');}
More details about background jobs are in the link below. This solves my problem. Thank you all.
PowerShell: Start-Job -scriptblock Multi line scriptblocks?

Related

C#: Launch of PowerShell script won't execute from a Form application

I'm writing a utility application which executes a powershell script, which further launches an .msi based installation after copying files from shared location. Calling this script Asynchronously using Powershell class.
This is working fine in a console application, but hangs when called from a C# Form via Button click.
Here is the execution of Form:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(true);
Application.Run(new frm_MyApp_Update());
Here is the method used to call the Powershell script:
private void ExecutePSFile(string scriptFile)
{
using (PowerShell PowerShellInstance = PowerShell.Create())
{
// 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(System.IO.File.ReadAllText(scriptFile));
//Synchronus call
//var psoutput = PowerShellInstance.Invoke();
// prepare a new collection to store output stream objects
PSDataCollection<PSObject> outputCollection = new PSDataCollection<PSObject>();
//Invoking ASynchrounsly, so that we can do something else, while it is being executed in a separate thread.
IAsyncResult result = PowerShellInstance.BeginInvoke<PSObject, PSObject>(null, outputCollection);
//let's do something else, while script is bing executed.
while (result.IsCompleted == false)
{
//Problem. Result.IsComplete is not getting true here :(
Thread.Sleep(1000);
}
}
}
It invokes the script, but then trap in while (result.IsCompleted==false) loop i.e result.IsCompleted not getting true here.
Please note that this is working fine in a Console based application.
Here is the Powershell script used here:
$fileName="MyInstall.msi"
$sourcePath="\\MyServer\SharedDir\Installers\"
$targetPath=$Env:TEMP + "\My_tmp\"
$logFile=$targetPath + $fileName + ".log"
New-Item -ItemType Directory -Path $targetPath -Force | Out-Null
Write-Host "Grabbing the latest installer..." -ForegroundColor Green
Copy-Item -Path #($sourcePath + $fileName) -Destination $targetPath -Force
Write-Host "Executing..." -ForegroundColor Green
$MsiArguments = #("/i " + """$targetPath\$fileName""" + " /log " + $logFile)
Start-Process "msiexec.exe" -ArgumentList $MsiArguments -Wait
Remove-Item $targetPath -Force -Recurse -ErrorAction Ignore| Out-Null
Write-Host "Finished Installation process..." -ForegroundColor Green
Is this a form threading issue?
What I'm missing here in Form based application?
Any help would be really appreciated....
https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.powershell.begininvoke
"When invoked using BeginInvoke, invocation doesn't
finish until Input is closed. Caller of BeginInvoke must
close the input buffer after all input has been written to
input buffer. Input buffer is closed by calling
Close() method.
If you want this command to execute as a standalone cmdlet
(that is, using command-line parameters only),
be sure to call Close() before calling BeginInvoke(). Otherwise,
the command will be executed as though it had external input.
If you observe that the command isn't doing anything,
this may be the reason."

Trouble with calling PS script (with parameter) from C#

I am trying to setup a simple .aspx web page that will accept a user's input of a string (later, more than one string) and use that string as the parameter value for a Powershell script.
The PS script looks like this right now:
[CmdletBinding()]
param (
[string] $ServiceName
)
$ServiceName | out-file c:\it\test.txt
$ServiceName | Out-String
The C# code looks like this:
var shell = PowerShell.Create();
// Add the script to the PowerShell object
shell.Commands.AddScript("C:\\it\\test.ps1 -ServiceName BITS");
// Execute the script
var results = shell.Invoke();
When I run that, I get "BITS" written to the test.txt file. What I need to do now, is setup the application to call the script, passing in the "ServiceName" parameter. I found this: Call PowerShell script file with parameters in C# and tried the following code:
PowerShell ps = PowerShell.Create();
ps.AddScript(#"c:\it\test.ps1").AddParameter("ServiceName", "BITS");
var results = ps.Invoke();
In this case, the script was called and the test.txt file was created, but the value (BITS) was not written to the file. What am I missing here? Why isn't the parameter being passed to the script?
Thanks.
I ended up using
var ps = #"C:\it\test.ps1";
processInfo = new ProcessStartInfo("powershell.exe", "-File " + ps + " -ServiceName BITS);
I don't like this as much, but it works. shrug
Here are three possible solutions for future readers including me:
using (PowerShell ps = PowerShell.Create())
{
//Solution #1
//ps.AddCommand(#"C:\it\test.ps1", true).AddParameter("ServiceName", "BITS");
//Solution #2
//ps.AddScript(#"C:\it\test.ps1 -ServiceName 'BITS'", true);
//Solution #3
ps.AddScript(File.ReadAllText(#"C:\it\test.ps1"), true).AddParameter("ServiceName", "BITS");
Collection<PSObject> results = ps.Invoke();
}
I haven't seen solution #3 documented anywhere else, though I got the idea for it from https://blogs.msdn.microsoft.com/kebab/2014/04/28/executing-powershell-scripts-from-c/

Unable to execute PowerShell script from batch file to run in CMD mode

I created a PowerShell script for FileWatcher. I need to execute it in C#. I tried in many ways but it didn't work. I even created a batch file to execute my script. still it is not working, simply the console gets opened and closed. but when i run each step manually in the command prompt I could able to execute the script.
Below is my PowerShell script:
$folder = 'D:\'
$filter = '*.csv'
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{
IncludeSubdirectories = $false;
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp" -Fore red
Out-File -FilePath D:\outp.txt -Append -InputObject "The file '$name' was $changeType at $timeStamp"
}
Here is the batch file
D:
powershell.exe
powershell Set-ExecutionPolicy RemoteSigned
./FileWatcherScript
To call a script from Powershell, you should use the -File argument. I'd change your batch file to look something like this - all you need is this one line:
powershell.exe -ExecutionPolicy RemoteSigned -File D:\FileWatcherScript.ps1
Starting powershell.exe without passing any arguments, as in the batch script in your post, will always start an interactive shell that must be exited manually. To run commands through Powershell programatically and exit when finished, you can pass the -File argument as I did above, or the -Command argument which takes a string or script block. Here is a short example of the -Command argument taking a string:
powershell.exe -Command "Invoke-RestMethod https://example.com/api/do/thing; Invoke-RestMethod https://example.com/api/stop/otherthing"
That invocation calls Invoke-RestMethod twice on two different URLs, and demonstrates that you can separate commands with a semicolon (;) to run one after another.
You can also pass a scriptblock to -Command, but note that this only works from within another Powershell session. That looks like this:
powershell.exe -Command { Invoke-RestMethod https://example.com/api/do/thing; Invoke-RestMethod https://example.com/api/stop/otherthing }
That invocation does the same thing as the previous one. The difference is that it is using a scriptblock - a Powershell construct, which again only works if the parent shell is also Powershell - and it's a bit nicer since you have fewer string quoting problems.

Why am I not getting any verbose output in my C# PowerShell streams?

I have instantiated a C# PowerShell object and added scripts like so:
var ps = PowerShell.Create();
ps.AddScript(#"& ""filename.ps1"" ""machine5"" ""stop"" ");
Collection<PSObject> results = ps.Invoke();
For reference, here are the contents of filename.ps1:
param([string] $middleTier, [string] $mode)
Write-Verbose "This does appear"
if($mode -eq "stop") {
Invoke-Command -ComputerName $middleTier -ScriptBlock {
Write-Verbose "This does not appear"
Stop-Service -Name "ARealService" -Force -Verbose
}
iisreset $middleTier /stop
}
However, after a call to ps.Invoke(), all of my ps.Streams collections are empty. I know for a fact that this script writes to the verbose stream when run, but the C# Powershell object does not seem to capture any of it. What am I doing wrong?
Edit: I have added some explicit calls to Write-Vebose to show what does and does not get captured. It is clear that there is a problem with getting the stream output from the Invoke-Command block.
Your script block has its own verbose preference ($VerbosePreference). Set it to Continue inside script block then it should work.

Powershell Invoke-Command ASPX/C# vs. Commandline

So I try to invoke a PS script from a ASPX/C# application. When I run the PS script from commandline (PS C:\scripts> ./do-barrelRoll.ps1 -s remoteComputer) it works as expected. But when I use C# it cannot connect to the remote computer (wrong user/password). Fun fact: The user and password to use are inside a config section in the PS script itself (see $SETUP variable)! What am I doing wrong?
Exact error message from ASPX (the server name is correct!):
[remoteComputer] Connecting to remote server remoteComputer failed with the following error message : The user name or password is incorrect
Relevant parts of the PS Script:
set-alias new New-Object
$passwd = $SETUP["password"] | ConvertTo-SecureString -asplaintext -force
$session = new -TypeName System.Management.Automation.PSCredential -argumentlist $SETUP["user"], $passwd
Invoke-Command –ComputerName $PARAM["s"] -Credential $session –ScriptBlock {
# Do a barrel roll
}
Relevant parts of ASPX/C# application:
using System.Management.Automation;
public ActionResult doAction(string server, string action)
{
var split = server.Split(';');
//...
var ps = PowerShell.Create();
ps.AddScript("Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -f; C:\\scripts\\do-barrelRoll.ps1 -s " + split[1]);
Collection<PSObject> results = ps.Invoke();
// Here I retrive the results and the content of the error stream and display them
}
It works as expected at the command line most likely because your account has full script access and can run everything. The account that .NET runs under should be more constrained. You'll have to check that the account has rights to run unrestricted scripts. Check local policies and then go up from there. You most likely are not the same account that the .NET application is running as.

Categories