I want to extract a zip file in Downloads folder to Desktop using PowerShell and C#.
I need it to work with Windows 7, 8, & 10.
I'm trying to take these PowerShell commands
extract https://stackoverflow.com/a/36472063/6806643
overwrite https://stackoverflow.com/a/5711383/6806643
chain https://superuser.com/a/612413/740888
$shell = New-Object -ComObject shell.application
$zip = $shell.NameSpace("zip file path")
foreach ($item in $zip.items()) {
$shell.Namespace("unzip destination path").CopyHere($item)
}
And run it through a C# Process.Start()
Process.Start("powershell.exe",
"timeout 3; "
+ "$shell = New-Object -ComObject shell.application; "
+ "$zip = $shell.NameSpace(\"C:\\Users\\Matt\\Downloads\\MyFile.zip\"); "
+ "foreach ($item in $zip.items()) {$shell.Namespace(\"C:\\Users\\Matt\\Desktop\\\").CopyHere($item, 0x14)}"
);
Problem
PowerShell launches but fails to extract, and closes out before I can read the error.
However, if I copy paste those chained commands into PowerShell without C#, it works.
$shell = New-Object -ComObject shell.application; $zip = $shell.NameSpace('C:\Users\Matt\Downloads\MyFile.zip'); foreach ($item in $zip.items()) {$shell.Namespace('C:\Users\Matt\Desktop\').CopyHere($item, 0x14)}
This works but it's for PowerShell 5 only.
Process.Start("powershell.exe",
"timeout 3; Expand-Archive 'C:\\Users\\Matt\\Downloads\\MyFile.zip' -Force -DestinationPath 'C:\\Users\\Matt\\Desktop\\'"
);
I may have solved it while writing out the question and refactoring other chained commands.
I found this article and combined it with the other code.
https://www.howtogeek.com/tips/how-to-extract-zip-files-using-powershell/
Process.Start("powershell.exe",
"-nologo -noprofile -command "
+ "timeout 3; "
+ "$shell = new-object -com shell.application; "
+ "$zip = $shell.NameSpace('C:\\Users\\Matt\\Downloads\\MyFile.zip'); "
+ "foreach ($item in $zip.items()) {$shell.Namespace('C:\\Users\\Matt\\Desktop\\').CopyHere($item, 0x14)}"
);
The differences are:
adding -nologo -noprofile -command
-com instead of -ComObject
single quotes ' instead of double " around paths.
And chaining messages with Write-Host \"hello\" -NoNewLine instead of echo.
Related
I am unable to get all the INSTALLED voices for a multi-lingual Text-to-Speech solution, although all installed languages are showing up. I know similar questions have been asked before but there don't seem to be any answers even yet except for some registry tweaks. The code is as under:
foreach (InputLanguage lang in InputLanguage.InstalledInputLanguages)
{
txtText.AppendText(Environment.NewLine + Environment.NewLine + lang.Culture.EnglishName);
using (SpeechSynthesizer synthesizer = new SpeechSynthesizer())
{
var voiceCollection = synthesizer.GetInstalledVoices(new CultureInfo(lang.Culture.Name));
foreach (InstalledVoice voice in voiceCollection)
{
VoiceInfo info = voice.VoiceInfo;
txtText.AppendText(Environment.NewLine + "Name: " + info.Name);
txtText.AppendText(Environment.NewLine + "Gender: " + info.Gender);
txtText.AppendText(Environment.NewLine + "Culture: " + info.Culture + Environment.NewLine);
}
}
}
I have found the solution. As you said, we have to modify the registry to get all the
voices successfully.
However, we don't need to modify it manually. You can run the following powershell code in
windows powershell.(Don't forget run as administrator for powershell)
$sourcePath = 'HKLM:\software\Microsoft\Speech_OneCore\Voices\Tokens' #Where the OneCore voices live
$destinationPath = 'HKLM:\SOFTWARE\Microsoft\Speech\Voices\Tokens' #For 64-bit apps
$destinationPath2 = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\SPEECH\Voices\Tokens' #For 32-bit apps
cd $destinationPath
$listVoices = Get-ChildItem $sourcePath
foreach($voice in $listVoices)
{
$source = $voice.PSPath #Get the path of this voices key
copy -Path $source -Destination $destinationPath -Recurse
copy -Path $source -Destination $destinationPath2 -Recurse
}
After running this, you can run your current winform app again. You can see all the
installed voices.
Here is my tested result:
I need help to solve problem to auto-mate deploy .Net Core web app Api to a server.
As you know is impossible to overwrite .dll if you not stop AppPool before , and there is no solution for that in IIS .
Actually by using PowerShell I can perform a script to do what I need the script is showed below:
PowerShell script
Actually I need a console application to performe same work , I found that I can use Microsoft.PowerShell.SDK to implement a solution.
public static void RunC()
{
string us = "xxxxxxxxxxxxx"; //User
string pw = "xxxxxxxxxxxxxxxx";//Passwprd
string sv = "xxx.x.xx.xxx";//Server
string apppoolname = "xxxxxxxxxxxxx";
StringBuilder script = new StringBuilder();
//Creazione script PS
script.Append("$password = ConvertTo-SecureString \"" + pw + "\" -AsPlainText -Force" + Environment.NewLine);
script.Append("$user = \"" + us + "\"" + Environment.NewLine);
script.Append("$cred = New-Object System.Management.Automation.PSCredential ($user,$password)" + Environment.NewLine);
script.Append("Enter-PSSession -ComputerName \"" + sv + "\" -Credential $cred" + Environment.NewLine);
script.Append("Import-Module webadministration");
script.Append("Stop-WebAppPool \"" + apppoolname + "\"");
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(script.ToString());
//pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
StringBuilder sb = new StringBuilder();
foreach (PSObject pSObject in results)
{
sb.AppendLine(pSObject.ToString());
}
Console.WriteLine(sb.ToString());
}
But I got error show in the image , seams Module is not lodaded or something similar ..
some one can help me in some way?
Thank you :-)
I dont see the error in the image, but if it is a issue caused by the WebAdministration not being imported this should fix it:
#Requires -Modules WebAdministration
Place that at the begging of the script and it will try to import the module if it's not available already in the session. Microsoft documentation:
Specifies PowerShell modules that the script requires. Enter the module name and an optional version number.
If the required modules aren't in the current session, PowerShell imports them. If the modules can't be imported, PowerShell throws a terminating error.
source
Hope it helps, good luck.
I'm trying to get some data from Azure Active Directory using C# code with reference to System.Management.Automation. I've got no errors with code execution, just null results and no output to textfile. Does anyone have this problem before or maybe I missed something? Thank you!
public void RunScriptTest()
{
string username = "Username";
string password = "Password";
List<String> listResults = new List<String>();
PowerShell powershell = PowerShell.Create();
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
powershell.Runspace = runspace;
powershell.AddScript("Install-Module -Name AzureAD -Force; \n");
powershell.AddScript("Import-Module -Name AzureAD -Verbose \n");
powershell.AddScript("$username = \"" + username + "\"; \n" +
"$password = convertTo-securestring '" + password + "' -AsPlainText -Force; \n" +
"$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $password; \n" +
"Connect-AzureAD - Credential $cred; \n");
powershell.AddScript("Get-AzureADUser | Out-File -FilePath " + #"C:\TestResults\1.txt");
Collection<PSObject> results = powershell.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach(PSObject obj in results)
{
listresults.Add(obj.ToString());
}
}
Sequencing .AddScript() calls without intervening .AddStatement() calls makes only the last .AddScript() call effective - all previous calls are ignored.
In order to examine errors that may have occurred during execution via .Invoke(), you must access the powershell.Streams.Error stream.
Therefore, the immediate fix is to replace your powershell.AddScript(...) calls with $powershell.AddStatement().AddScript(...)
Note that your PowerShellCode appears designed not to produce any output, so there's no point in trying to populate listresults.
I have a PowerShell script which communicates with a REST server. This script only works in PowerShell 6.
I want to call it from C#, because the C# program needs the info from the REST server, and I don't want to rewrite the REST code in C#.
So basically, I want to run a PowerShell script from C#. However, in C#, PowerShell.Create(); creates a PowerShell instance that uses PowerShell 5.
I already replaced pwsh.exe in the default folder, deleted PowerShell 5 everywhere etc. and when I shift+right click anywhere to use "Run PowerShell here" I get a PowerShell 6 window. But for some reason, C# sticks to using PowerShell 5, when using the PowerShell class.
This is the PowerShell code I want to reuse:
function Get-JSONWebToken {
param (
[Parameter(Mandatory=$True)][string] $BaseUri,
[Parameter(Mandatory=$True)][string] $ApiToken
)
if ($PSVersionTable.PSVersion.Major -lt 6) {
$version = $PSVersionTable.PSVersion
Throw "Your PowerShell version is: $version. Please upgrade to PowerShell 6 or above"
}
$uri = "$BaseUri/auth/token"
$bodyJson = ConvertTo-Json #{token = $ApiToken} -Compress
Write-Host "Authenticating ..."
try {
$response = Invoke-RestMethod `
-Uri $uri `
-Method Post `
-ContentType "application/json" `
-Body $bodyJson
$jwtToken = $response.token
$secureToken = ConvertTo-SecureString $jwtToken -AsPlainText -Force
return $secureToken
}
catch {
#handle error
}
}
So now I am trying to call PowerShell 6 manually, importing a module first and then using it. Here are my three attempts, which are all supposed to do the same thing: call Get-JSONWebToken (in rest-api.psm1) and retrieve the output correctly.
C# version 1, using PowerShell class:
ps = PowerShell.Create();
//module import...
PSCommand cmd = ps.Commands.AddCommand("Get-JSONWebToken");
cmd.AddParameter("baseUri", baseUri);
cmd.AddParameter("apiToken", apiToken);
ps.Invoke();
This always runs on PowerShell 5 for some reason so it can't be used.
C# version 2, using a Process instead
Process ps6 = new Process();
ps6.StartInfo = new ProcessStartInfo {
FileName = "C:/Program Files/PowerShell/6/pwsh.exe",
Arguments = "-Command {\n" +
"Import-Module " + modulePath + ";\n" +
"Get-JSONWebToken " + apiToken + ";\n" +
"}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = false
};
ps6.Start()
This runs on PowerShell 6, but only outputs the arguments I passed, and not the output of Get-JSONWebToken.
C# version 3: Calling PS6 from PS5 from C#
PSCommand cmd = ps.Commands.AddCommand("C:/Program Files/PowerShell/6/pwsh.exe");
ScriptBlock sb = ScriptBlock.Create("Import-Module " + modulePath + "; Get-JSONWebToken " + apiToken + ";");
cmd.AddParameter("Command", sb);
ps.Invoke();
This doesn't work at all:
Result: Usage: pwsh[.exe] [[-File] <filePath> [args]]
Result: [-Command { - | <script-block> [-args <arg-array>]
Result: | <string> [<CommandParameters>] } ]
Result: [-ConfigurationName <string>] [-CustomPipeName <string>]
...
...
PowerShell version:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $Ps6Path
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.CreateNoWindow = $false
$pinfo.Arguments = "-Command {Import-Module <myPath>\rest-api.psm1; Get-JSONWebToken 123inputStringExample;}"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
This also only outputs the arguments I passed when called either from C# or from PS6 or PS5
This doesn't technically solve the problem, but I did as #MindSwipe suggested and rewrote the code in C# entirely. It wasn't trivially easy but it's a nice and elegant solution in the end.
If you have an idea on how to solve this question properly, please post it here as I'm still interested in how to call stuff on PowerShell 6 from C#.
Really struggling with this. I have tried various different way, but nothing seems to work.
-using addScript: I get an error telling me that I can't call parameters this way an should use a UI like ISE ?!
-using FilePath parameter, I can't find the right way to pass the arguments (trouble binding)
This is the latest version I tried, and is lifting no errors, but the script is not executed, nothing happens...
Help would be much appreciated.
runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
pipeline = runspace.CreatePipeline();
string script =
#"{param($merchantName, $appType, $gruntDirectory, $merchantInstanceDirectory, $editorConnectionString) "+
_config.MerchantInstance.Directory + #"\Generate_And_Compile_LESS.ps1"
+ " –merchantName $merchantName"
+ " –appType $appType"
+ " –gruntDirectory $gruntDirectory"
+ " -merchantInstanceDirectory $merchantInstanceDirectory"
+ " -editorConnectionString $editorConnectionString }";
Command compileCommand = new Command("Invoke-Command");
compileCommand.Parameters.Add("Scriptblock", ScriptBlock.Create(script));
var args = new List<string>();
args.Add(merchantName);
args.Add(appType.GetHashCode().ToString());
args.Add("'" + _config.Grunt.Directory + "'");
args.Add("'" + _config.MerchantInstance.Directory + "'");
args.Add("'" + _connectionStrings.AppConnectionString + "'");
compileCommand.Parameters.Add("ArgumentList", String.Join(",", args));
pipeline.Commands.Add(compileCommand);
Collection<PSObject> results = pipeline.Invoke();
You can use this code, which I personally just tested.
static void Main(string[] args)
{
PowerShell ps = PowerShell.Create();
ps.AddScript(#"c:\test\test.ps1").AddParameter("param1", "paramvalue1");
ps.Invoke();
}
Here is my test script, located in c:\test\test.ps1.
[CmdletBinding()]
param (
[string] $param1
)
Set-Content -Path $PSScriptRoot\test.txt -Value $param1;
FYI, make sure that you launch 32-bit (x86) PowerShell, and set the execution policy to Unrestricted. Visual Studio is a 32-bit process, and invokes the 32-bit PowerShell engine by default.