execute powershell commands from ruby - ruby

I was trying to execute a powershell command from ruby code.
Get-WmiObject -Class Win32_Product -ComputerName . -Filter "Name='Qlik Sense Client'" |Select-Object -Property version
It gives me the version of the product perefectly. But the same thing when i try to execute from ruby like(entire command in backticks) :
find = powershell.exe Get-WmiObject -Class Win32_Product -ComputerName . -Filter "Name=''"|Select-Object -Property version
The command breaks, it cant interpret the quotes, pipes etc in ruby. I tried to escape those quotes but it still it breaks. I dont know how to escape that pipe as well.
Kindly help me here or refer me to something relevant. Many thanks.

I have tested this now:
require 'base64'
cmd = %{Get-WmiObject -Class Win32_Product -ComputerName . -Filter "Name='Qlik Sense Client'"|Select-Object -Property version}
encoded_cmd = Base64.strict_encode64(cmd.encode('utf-16le'))
find = `powershell.exe -encodedCommand #{encoded_cmd}`
Powershell expects UTF-16LE-encoded strings, so you have to convert from Ruby's encoding (UTF-8) before the base64 conversion.
Alternatively you could try using shellescape from shellwords to escape the command so the shell interprets it as a single string.
Another alternative is to use powershell.exe -Command - with popen3. That will let you write your commands and read their results using a file stream.

Here an example of how I shell to external apps, should also work with Powershell. Copy the working command from your console between the {} without extra delimiters. The %Q also makes it possible to do interpolation of variables in case your command is not always the same, it works the same way as between the " delimiter.
All the output is captured by the " 2>&1" and enumerated line by line in the block after while.
In case more than one line is captured you need to check which line the result you are after is displayed and return the result.
def powershell
command = %Q{your command just like you execute it in a console}
IO.popen(command+" 2>&1") do |pipe|
pipe.sync = true
while lijn = pipe.gets
# do whatever you need with the output of the command
# return the result
end
end
end
Use the %q{} alternative if you don't need the interpolation since that also could give problems. Use the Ruby comamnd in the same console as where you get your powershell results. Ensure you can run powershell from there (must be in the path).
But as I understand this will get you the name and version of the installed products on your pc.
Why not use just Ruby ? It's way more faster than the VERY slow Wmi queries.
require 'win32/registry'
Win32::Registry::HKEY_LOCAL_MACHINE.open(
'Software\Microsoft\Windows\CurrentVersion\Uninstall'
) do |reg|
reg.each_key do |key|
k = reg.open(key)
puts key
puts k["DisplayName"] rescue "?"
puts k["DisplayVersion"] rescue "?"
puts
end
end

Related

How do I get ReadLineAsSecureString() to accept pipe input?

When I am using cmd and pipe to a powershell which uses ReadLine() to read input, it accepts the pipe input as expected:
C:\Users\ohnob>ECHO hi|powershell -Command "write-host $host.UI.ReadLine()"
hi
hi
However, when am using cmd and pipe to powershell using ReadLineAsSecureString(), it hangs until I type return into the terminal:
C:\Users\ohnob>ECHO hi|powershell -Command "write-host $host.UI.ReadLineAsSecureString()"
I need to read as a secure string (using asterisks) when the session is interactive. But I need to read pipe input when there is pipe input. How do I accomplish this in powershell? I expect I could be able to detect whether or not stdin is a pipe somehow and then conditionally use ReadLine() instead of ReadLineAsSexcureString(), but I don’t know how to get a handle of standard input in powershell—I only get access to this PSHostUserInterface object.
EDIT: To clarify, when I use ReadLineAsSecureString() or ReadLine(), I expect it to read one line from input. That allows it to be used multiple times and the user to provide multiple values to the script. I expect any answers to be drop-in replacements for the functions I use except without the issues that I am trying to avoid. Thanks!
Jeroen Mostert makes some good points in the comments, notably that $host.UI.ReadLineAsSecureString() / Read-Host -AsSecureString presumably by design do not accept pipeline input, for security reasons[1].
Therefore, you must explicitly distinguish between receiving pipeline input and not receiving any:
PowerShell provides access to stdin input piped from the outside via the automatic $Input variable.
$MyInvocation.ExpectingInput generally indicates if pipeline input is present.
If there is pipeline input, pass its first line[2] to ConvertTo-SecureString;
if there isn't, call Read-Host -AsSecureString:
$secStr = if ($MyInvocation.ExpectingInput) {
# Alternatively, use $firstLine = [console]::ReadLine() - thanks, #binki
$firstLine = $($null = $Input.MoveNext(); $Input.Current)
ConvertTo-SecureString -AsPlainText -Force $firstLine
} else {
Read-Host -AsSecureString -Prompt 'Enter secret'
}
As a command line to call from cmd.exe that also outputs the result:
C:>echo hi| powershell -Command "$secStr = if ($MyInvocation.ExpectingInput) { $firstLine = $($null = $Input.MoveNext(); $Input.Current); ConvertTo-SecureString -AsPlainText -Force $firstLine } else { Read-Host -AsSecureString -Prompt 'Enter secret' }l$secStr"
Note, however, that secure strings by design print generically as System.Security.SecureString, so that's all you'll see.
[1] I'm speculating in the absence of documentation on that aspect. Piping a plain-text string to Read-Host -AsSecureString definitely has a greater potential of being insecure: the plain-text string being piped may be persisted that way somewhere and, at least hypothetically, if you use something like MyCustomEcho.exe secret | ..., the process' command line will reflect the secret information.
Either way, passing plain text to ConvertTo-SecureString -AsPlainText is always an option, where the additional need to pass -Force also indicates that PowerShell considers using plain-text input that wasn't typed interactively.
[2] The automatic $Input variable is an enumerator (type ) that enumerates lines from stdin on demand. Indexed access such as $Input[0] is not supported; instead, you must use .MoveFirst() to start the enumeration and then access the .Current property to get the first line. That way, the remaining elements aren't consumed and later use of $Input yields the remaining lines.
The only reason for the $(...) around $null = $Input.MoveNext(); $Input.Current is so that the two statements can be wrapped in a single statement that returns the first line, for conceptual clarity; using $null = $Input.MoveNext() first and $firstLine = $Input.Current is perfectly fine also.

Converting unix script to windows script - emulating a Sed command in PowerShell

I have a unix script (korn to be exact) that is working well and I need to convert it windows batch script. So far I have tried inserting a powershell command line on my code, but it doesn't work. Please help, I am just new to both unix scripting and windows scripting so any help will do.
This is the line of code that I need to convert:
#create new file to parse ; exclude past instances of timestamp
parsefile=/tmp/$$.parse
sed -e "1,/$TIMESTAMP/d" -e "/$TIMESTAMP/d" $DSTLOGFILE > $parsefile
So far I have tried a powershell command line to be called on my script but it didn't work:
:set_parse_file
#powershell -Command "Get-Content $SCHLOGFILE | Foreach-Object {$_ -replace('1,/"$TIMESTAMP"/d' '/"$TIMESTAMP"/d'} | Set-Content $PARSEFILE"
Any suggestions please?
PowerShell has no sed-like constructs for processing ranges of lines (e.g., sed interprets 1,/foo/ as referring to the range of consecutive lines from line 1 through a subsequent line that matches regex foo)
Emulating this feature with line-by-line processing would be much more verbose, but a comparatively more concise version is possible if the input file is processed as a whole - which is only an option with files small enough to fit into memory as a whole, however (PSv5+ syntax).
Here's the pure PowerShell code:
$escapedTimeStamp = [regex]::Escape($TIMESTAMP)
(Get-Content -Raw $SCHLOGFILE) -replace ('(?ms)\A.*?\r?\n.*?' + $escapedTimeStamp + '.*?\r?\n') `
-replace ('(?m)^.*?' + $escapedTimeStamp + '.*\r?\n') |
Set-Content -NoNewline $PARSEFILE
Note that [regex]::Escape() is used to make sure that the value of $TIMESTAMP is treated as a literal, even if it happens to contain regex metacharacters (chars. with special meaning to the regex engine).
Your ksh code doesn't do that (and it's nontrivial to do in ksh), so if - conversely - $TIMESTAMP should be interpreted as a regex, simply omit that step and use $TIMESTAMP directly.
The -replace operator is regex-based and uses the .NET regular-expression engine.
It is the use of Get-Content's -Raw switch that requires PSv3+ and the use of Set-Content's -NoNewline switch that requires PSv5+. You can make this command work in earlier versions, but it requires more effort.
Calling the above from cmd.exe (a batch file) gets quite unwieldy - and you always have to be wary of quoting issues - but it should work:
#powershell.exe -noprofile -command "$escapedTimeStamp = [regex]::Escape('%TIMESTAMP%'); (Get-Content -Raw '%SCHLOGFILE%') -replace ('(?ms)\A.*?\r?\n.*?' + $escapedTimeStamp + '.*?\r?\n') -replace ('(?m)^.*?' + $escapedTimeStamp + '.*\r?\n') | Set-Content -NoNewline '%PARSEFILE%'"
Note how the -command argument is passed as a single "..." string, which is ultimately the safest and conceptually cleanest way to pass code to PowerShell.
Also note the need to embed batch variables as %varname% in the command, and since they are enclosed in embedded '...' above, the assumption is that their values contain no ' chars.
Therefore, consider implementing your entire script in Powershell - you'll have a much more powerful scripting language at your disposal, and you'll avoid the quoting headaches that come from bridging two disparate worlds.

How do you resolve special folders in path strings in PowerShell?

In the Windows Command Prompt, special folders are resolved like so:
However, in powershell, these folders do not seem to be resolved:
Consider the string:
$myfile = "%temp%\\myfolder\\myfile.txt"
How can I use this as an argument to PowerShell functions (eg: Remove-Item), and have PowerShell correctly resolve the special folders, as opposed to taking it literally and prepending the current working directory?
Edit:
I am working with strings using standard windows path notation coming from external configuration files, for example:
config.json:
{
"some_file": "%TEMP%\\folder\\file.txt"
}
myscript.ps1:
$config = Get-Content -Raw -Path "config.json" | ConvertFrom-Json
Remove-Item -path $config.some_file -Force
Note: as any of the Windows special folders can appear in these strings, I'd rather avoid horrible find-replace hacks like this
$config.some_file = $config.some_file -replace '%TEMP%' $env:temp
You can expand it to a full path using:
[System.Environment]::ExpandEnvironmentVariables("%TEMP%\\myfolder\\myfile.txt")
c:\users\username\AppData\Local\Temp\\myfolder\\myfile.txt
Double-backslash \\ isn't a PowerShell thing either, \ is not a special character in a PowerShell string - but double backslashes in a path do seem to work.
Documentation: https://msdn.microsoft.com/en-us/library/system.environment.expandenvironmentvariables.aspx
If you don't mind some performance issues
$resolvedPathInABitHackyWay = (cmd /c echo "%TEMP%\\folder\\file.txt")
This will actually give you %TEMP% resolved by cmd itself.
You can grab all env variables from the env:\ drive and use that to construct a succinct regex pattern for your find-replace operation, then use the Regex.Replace() method with a match evaluator:
$vars = Get-ChildItem env:\ |ForEach-Object {[regex]::Escape($_.Name)}
$find = "%(?:$($envNames -join '|'))%"
[regex]::Replace($config.some_file, $find, {param([System.Text.RegularExpressions.Match]$found) return (Get-Item "env:\$($found.Groups[1])").Value},'IgnoreCase')

How to do a "sed -" like operation on Windows?

I have a large 55 GB file in which there is a sentence on every line.
I want to check if there are any lines that have a dot "." at the end, and then if there is, I want to insert a space before the dot in that line.
Ex: I like that car.
Replace with: I like that car .
A space before the trailing dot on every line if there is a dot.
I don't have any cygwin or unix and I use a windows OS. Is there a sed like common that I can do on this 55GB! file?
I tried GetGNUWin32 but I am unable to determine the actual command there.
Install Perl. Strawberry Perl is probably the best distribution for Windows. http://strawberryperl.com/
To do what you're talking about in Perl, it would be this:
perl -p -i -e's/\.$/ ./' filename
You can install Cygwin and use sed from there. And here I found Sed for Windows
Edit:
Very Good Answers to your Question:
Is there any sed like utility for cmd.exe
(I always prefix stackoverfloew when I search on google. Same I did for you on google: sed on window stackoverflow, but that is different matter)
For your use case:
From PowerShell.exe (comes with Windows)
(Get-Content file.txt) -Replace '\.$', ' .' | Set-Content file.txt
I searched for hours and hours and had so much trouble trying to find a solution to my use case, so I hope adding this answer helps someone else in the same situation.
For those who got here to figure out git filter clean/smudge like I did, here's how I finally managed it:
In file: .gitconfig (global)
[filter "replacePassword"]
required = true
clean = "PowerShell -Command \"(Get-Content " %f ") -Replace 'this is a password', 'this is NOT a password'\""
smudge = "PowerShell -Command \"(Get-Content " %f ") -Replace 'this is NOT a password', 'this is a password'\""
Please note that this snippet doesn't change the original file (this is intended for my use case).
Additional search terms to help those looking: interpolation, interpolate

Convert file from Windows to UNIX through Powershell or Batch

I have a batch script that prompts a user for some input then outputs a couple of files I'm using in an AIX environment. These files need to be in UNIX format (which I believe is UTF8), but I'm looking for some direction on the SIMPLEST way of doing this.
I don't like to have to download extra software packages; Cygwin or GnuWin32. I don't mind coding this if it is possible, my coding options are Batch, Powershell and VBS. Does anyone know of a way to do this?
Alternatively could I create the files with Batch and call a Powershell script to reform these?
The idea here is a user would be prompted for some information, then I output a standard file which are basically prompt answers in AIX for a job. I'm using Batch initially, because I didn't know that I would run into this problem, but I'm kind of leaning towards redoing this in Powershell. because I had found some code on another forum that can do the conversion (below).
% foreach($i in ls -name DIR/*.txt) { \
get-content DIR/$i | \
out-file -encoding utf8 -filepath DIR2/$i \
}
Looking for some direction or some input on this.
You can't do this without external tools in batch files.
If all you need is the file encoding, then the snippet you gave should work. If you want to convert the files inline (instead of writing them to another place) you can do
Get-ChildItem *.txt | ForEach-Object { (Get-Content $_) | Out-File -Encoding UTF8 $_ }
(the parentheses around Get-Content are important) However, this will write the files in UTF-8 with a signature at the start (U+FEFF) which some Unix tools don't accept (even though it's technically legal, though discouraged to use).
Then there is the problem that line breaks are different between Windows and Unix. Unix uses only U+000A (LF) while Windows uses two characters for that: U+000D U+000A (CR+LF). So ideally you'd convert the line breaks, too. But that gets a little more complex:
Get-ChildItem *.txt | ForEach-Object {
# get the contents and replace line breaks by U+000A
$contents = [IO.File]::ReadAllText($_) -replace "`r`n?", "`n"
# create UTF-8 encoding without signature
$utf8 = New-Object System.Text.UTF8Encoding $false
# write the text back
[IO.File]::WriteAllText($_, $contents, $utf8)
}
Try the overloaded version ReadAllText(String, Encoding) if you are using ANSI characters and not only ASCII ones.
$contents = [IO.File]::ReadAllText($_, [Text.Encoding]::Default) -replace "`r`n", "`n"
https://msdn.microsoft.com/en-us/library/system.io.file.readalltext(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.text.encoding(v=vs.110).aspx
ASCII - Gets an encoding for the ASCII (7-bit) character set.
Default - Gets an encoding for the operating system's current ANSI code page.

Resources