Powershell does not render my script help correctly - powershell

I have a Powershell script with rather a big help at the beginning of it to be displayed with the Get-Help command - https://gist.github.com/MarkKharitonov/91698caf8c695902eacde2b6c7825bf1
I have two problems with the way the help is rendered. Specifically with the .EXAMPLE section. Here is an abbreviated version of the help (see the hyperlink for the full version of the script and the help):
<#
.SYNOPSIS
Runs a WinDBG command or a script file.
...
.NOTES
The default is to scan for images using the .imgscan /l. One has to check first with the WinDBG GUI to see if this is necessary, because this is a time consuming operation.
.EXAMPLE
Please, find below a PowerShell session transcript showing the script invoked with the -Command parameter:
PS D:\tmp\cantestr52 - 06-09-2017> cat C:\Users\mkharitonov\runcdb.config.ps1
$Sosex = "E:\Utils\sosex\64\sosex.dll"
$CDB = "e:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"
$SymbolsCache = "E:\Symbols"
PS D:\tmp\cantestr52 - 06-09-2017> cat .\runcdb.config.ps1
$dump = "d:\tmp\cantestr52 - 06-09-2017\Quartz.Server.DMP"
$ImagePath = "d:\tmp\cantestr52 - 06-09-2017\BJE"
$NoScanForImages = $false
PS D:\tmp\cantestr52 - 06-09-2017> runcdb.ps1 -Command "!dumpheap -mt 00007fff11d90f78 -live"
...
PS D:\tmp\cantestr52 - 06-09-2017>
The script produced two log files:
c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log.init
c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log
Here is the WinDBG code executed by the script:
| .logopen "c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log.init"
| .imgscan /l
| .load E:\Utils\sosex\64\sosex.dll
| !lhi
| .logclose
| .logopen "c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log"
| !dumpheap -mt 00007fff11d90f78 -live
| .logclose
| q
#>
Problem 1
Notice that the last lines start with |. This is because of .logopen - PowerShell attempts to treat it as a help section keyword, like .NOTES or .EXAMPLE. Of course, there is no such keyword and so PowerShell just truncates the help output and stops short of showing the last lines. I had to escape the dot somehow, but could not figure out the way.
Problem 2
Please, observe:
Notice the C:\PS> in the output. Of course, it is not part of my help.
So, two questions - how to escape the first dot in a help line and what is the source of the C:\PS> in the help output above?

Problem 1 - (lines beginning with dot .)
I don't think there's any getting around this with comment based help, but if you use XML based help then I don't think it would be an issue. That's a big change though.
Problem 2 - (prompt string)
It seems to do this with all examples, and isn't likely to be avoided with XML help. For what it's worth, I don't like this behavior either.
However, I will say that your example, I think, does not properly follow the format of what an example is. Your explanation that looks awkward in front of a prompt shouldn't be in the example section.
After .EXAMPLE you should just put the line of code (a short explanation could maybe go after the code).
More explanation can be given in .NOTES, or in linked documentation.

Related

How should I write a Powershell script to execute a single program on multiple files?

I'm using Kalles' Fraktaler on Windows 10 to render images of the Mandelbrot set. Bundled with KF is a program to take a single parameter file and beak it into multiple tiles for easier rendering.
The output for the tiling program is multiple files with the following naming scheme: name-0000-0000.kfr, name-0000-0000.kfs, where the name can be anything and the numbers increment as needed.
The .kfr files are the parameter files.
The .kfs files are the settings files.
After I have these generated parameter and setting files, I can execute KF on the command line with the following arguments:
kf.exe -s name-0000-0000.kfs -l name-0000-0000.kfr -p name-0000-0000.png
Doing this for every pair of parameter and setting files works perfectly fine, taking the input files and saving the render to name-0000-0000.png
I asked the developer for an example PowerShell script to automate the process for when there are dozens or more of the files that need to be rendered, and this is what he gave me. The script needs to be run from the same directory as the files are stored.
Get-ChildItem "." -Filter *.kfr |
Foreach-Object {
$kfr = $_.FullName
$kfs = $kfr.replace("kfr", "kfs")
$png = $kfr.replace("kfr", "png")
C:/path/to/kf.exe -s $kfs -l $kfr -p $png
}
Unfortunately, I've tried every variation of this script that I could think of, and nothing gives me any results. I have already allowed unsigned scripts to be run on my computer. I would greatly appreciate some help on this.
(PowerShell is nice and flexible - but only when you use it to invoke only PowerShell commands rather than running native executables. For example, to run a program in the current directory you need to prefix the program's name with ./ - ostensibly this is done for safety and I assume for similarity to Unix shells, but it's the first in a long list of gotchas for anyone wanting to use PowerShell for tasks that would be trivial in old-school batch files)
Anyway, you need to use Invoke-Command or Start-Process.
I've changed your script from using a piped expression into an easier-to-digest loop (and invoking .NET's Path.ChangeExtension directly because PowerShell's built-in string match-and-replace syntax is too arcane for me):
$kfrFiles = Get-ChildItem "." -Filter "*.kfr"
foreach ( $kfrFile in $kfrFiles ) {
$kfr = $kfrFile.Name
$kfs = [System.IO.Path]::ChangeExtension( $kfrFile.Name, "kfs" )
$png = [System.IO.Path]::ChangeExtension( $kfrFile.Name, "png" )
Start-Process -FilePath "C:\path\to\kfs.exe" -ArgumentList "-s $kfs", "-l $kf", "-p $png" -Wait
}
The -Wait option will wait for the kfs.exe program to finish before starting the next instance - otherwise if you have hundreds of .kfr files then you'll end-up with hundreds of kfr processes running concurrently.
I don't know how to allow concurrent processes but impose a limit on the maximum-number of concurrent processes in PowerShell. It is possible, just complicated.

Powershell fails to run multi-line commands from stdin?

I'm wanting to pass arbitrary scripts to Powershell via stdin.
(In practice, I'd like to avoid having to put the script into a temporary file, but for the purposes of this question I will pipe the contents of a file to powershell.)
So I'm doing something like so (in this example, from a Windows cmd shell):
type myfile.txt | powershell -
It works if myfile.txt contains something like this:
1..3 | % { $_ *2 }
echo done
(It outputs 2\n4\n6\ndone.)
However, if I split this first statement across multiple lines like so, then Powershell simply exists without generating any output at all:
1..3 |
% { $_ *2 }
echo done
This seems to fail for any multiline statement. For example, this also fails to produce output:
1..3 | % {
$_ *2 }
echo done
I'm surprised by this since each are legal Powershell scripts that would work normally if placed into a .ps1 file and run as normal.
I've tried various things including escaping the EOL using line continuation chars, to no avail. The same effect occurs if the parent shell is Powershell, or even Python (using subprocess.Popen with stdin=PIPE). In each case, Powershell exits without any error, and the exit code is 0.
Interestingly, if I run the following, only "before.txt" gets created.
"before" | out-file before.txt
1..3 |
% { $_ *2 }
"after" | out-file after.txt
echo done
Any ideas why Powershell would have trouble reading a multi-line command, if read from stdin?
I'm going to consider this answered by this:
How to end a multi-line command in PowerShell since it shows that an extra newline is required.
However, I'm going to raise this to MS as a bug since this should not be required when reading from a non-tty, or when -NonInteractive switch is specified.
Please vote on my bug report to the Powershell team.
This is not a complete answer, but from what I can tell, the problem has to do with the input being sent in line by line.
To demonstrate the line-by-line issue, I invoke powershell this way:
powershell.exe -command "gc myfile.txt" | powershell.exe -
vs
powershell.exe -command "gc myfile.txt -raw" | powershell.exe -
The first example replicates what you see with type, the second reads the entire contents of the file, and it works as expected.
It also works from within PowerShell if you put the script contents in a string and pipe it into powershell.exe -.
I had a theory that it had to do with line-by-line input lacking line breaks, but it's not so clear cut. If that were the case, why would the first option work but not the second (removing the line break splitting the single pipeline should have no effect, while removing the line break between the pipeline and the echo should make it fail). Maybe there's something unclear about the way powershell is handling the input with or without line breaks.

Call a program from Powershell w/ very long, variable argument list?

I am currently trying to convert a series of batch files to powershell scripts. I would like to run a compiler for the source files that exist in a directory, recursively. The compiler requires a long list of arguments. The catch is, I want the arguments to be variable so I can change them as needed. This is a typical call from the batch file (simplified for readability and length):
"C:\PICC Compilers\picc18.exe" --pass1
"C:\Src Files\somefile.c"
"-IC:\Include Files" "-IC:\Header
Files" -P
--runtime=default,+clear,+init,-keep,+download,+stackwarn,-config,+clib,-plib
--opt=default,+asm,-speed,+space,9 --warn=0 --debugger=realice -Blarge --double=24 --cp=16 -g --asmlist "--errformat=Error [%n] %f; %l.%c
%s" "--msgformat=Advisory[%n] %s" --OBJDIR="C:\Built Files"
"--warnformat=Warning [%n] %f; %l.%c %s"
This command executes fine when included in a batch file, but I start getting errors when I copy and paste the command into powershell. This is only my second day working with powershell, but I have developed with .NET in the past. I have managed to scrape together the following attempt:
$srcFiles = Get-ChildItem . -Recurse -Include "*.c"
$srcFiles | % {
$argList = "--pass1 " + $_.FullName;
$argList += "-IC:\Include Files -IC:\Header Files -P --runtime=default,+clear,+init,-keep,+download,+stackwarn,-config,+clib,-plib --opt=default,+asm,-speed,+space,9 --warn=0 --debugger=realice -Blarge --double=24 --cp=16 -g --asmlist '--errformat=Error [%n] %f; %l.%c %s' '--msgformat=Advisory[%n] %s' '--warnformat=Warning [%n] %f; %l.%c %s"
$argList += "--OBJDIR=" + $_.DirectoryName;
&"C:\PICC Compilers\picc18.exe" $argList }
I know that I probably have multiple issues with the above code, namely how to pass arguments and how I am dealing with the quotes in the argument list. Incorrect as it is, it should illustrate what I am trying to achieve. Any suggestions on where to start?
Calling command line applications from PowerShell might be really tricky. Several weeks ago #Jaykul wrote great blog post The problem with calling legacy/native apps from PowerShell where he describes gotchas which people will meet in this situations. And there is of course solution too ;)
edit - correct url
The article is no more available, so it's only possible to see that through web.archive.org - see cached article
Make $arglist an array instead of a string. A single string will always be passed as a single argument which is what you don't want here.

How do I reference variables when executing a shell command in PowerShell?

I'm a newbie to PowerShell. What's wrong with my script below? It's not wanting to emit the value of $config. However, when I wrap that command in double quotes, everything looks okay.
param($config, $logfolder)
# Must run log analysis in chronological order.
ls $logfolder | Sort-Object LastWriteTime | % {
perl D:\Websites\_awstats\wwwroot\cgi-bin\awstats.pl -LogFile="$($_.FullName)" -config=$config update
}
# Execute with - .\regen-logs.ps1 webgenesis "C:\inetpub\logs\LogFiles\W3SVC5"
# Returns for each file - Error: Couldn't open config file "awstats.config.conf" nor "awstats.conf" after searching in path "D:\Websites\_awstats\wwwroot\cgi-bin,/etc/awstats,/usr/local/etc/awstats,/etc,/etc/opt/awstats": No such file or directory
As-is, what gets emitted and executed seems to have "-config=$config" passed as an argument. At least, that's my best guess. I don't know if $_ is working correctly either.
If I put quotes around the perl command like so, I get the command I do want to execute.
ls $logfolder | Sort-Object LastWriteTime | % {
"perl D:\Websites\_awstats\wwwroot\cgi-bin\awstats.pl -LogFile=`"$($_.FullName)`" -config=$config update"
}
# Outputs for each log file something like - perl D:\Websites\_awstats\wwwroot\cgi-bin\awstats.pl -LogFile="C:\inetpub\logs\LogFiles\W3SVC5\u_ex110602.log" -config=webgenesis update
If putting quotes around it produces the correct commandline, one way to execute the contents of a string is with Invoke-Expression (alias iex):
$v = "myexe -myarg1 -myarg2=$someVar"
iex $v
Put double quotes around "-config=$config". Without this, PowerShell will interpret -config=$config as one string argument that just happens to contain a $ sign in it.
I think you need to start your perl command out with & so that PowerShell interprets things as a command and not a string.
& perl D:\Websites\_awstats\wwwroot\cgi-bin\awstats.pl -LogFile=`"$($_.FullName)`" -config=$config update
Also, see: Run a program in a foreach

Parsing to simulate "grep -C 2" in PowerShell version 1.0

I'm trying to dig through some logs and need information before and after the line I can match on. How would I do this in PowerShell ala "grep -C 2"?
In version 1, I can't wait for r2, then I get to put it on production machines :)
The PowerShell equivalent of grep is select-string. You can use the following.
cat file | select-string "pattern" -context 2
Note: this works in PowerShell v2.0 only.
Instead of using (gc $bigfile) again, which will cause PowerShell to read in $bigfile to memory on every object piped to it by the ForEach-Object cmdlet, you should probably read the file into a variable and then array index from that, like so:
$bigfile = gc 'c:\scripts\bigfile.txt'
$bigfile | Select-String "melissao" | % {$bigfile[($_.LineNumber -3)..($_.LineNumber +1)]}
Also, since the line numbering starts at 1 and array indexing starts at 0 you'll have to go backwards by 3, not 2, to get the line two spaces above "melissao", and go forwards by 1, not 2, to get the line two spaces below "melissao." Doing this will net you the 5 lines you want, "melissao" flanked by the two lines above and below it.
I'm not super familiar with grep -C 2, so I don't know if this replicates that functionality exactly.
Alas, it looks like you will have to build it yourself or wait for the next version of powershell, as it seems to have been implemented there. You can download a pre-release version of Powershell 2.0 from here.
Getting closer here- because Select-String returns MatchInfo objects which I can pull a line number out of (39017), now I just need to pull the line surrounding... so:
gc $bigfile | Select-String melissao |
%{(gc $bigfile)[($_.LineNumber -2)..($_.LineNumber +2)]}
If anyone could clean this up a bit to make it less slow, you may have the answer. Otherwise, this solution works but obviously not quickly.
Download grep for Windows, and call grep from PowerShell?