Wednesday, August 3, 2016

[Powershell] some basic blocks of code written this week.

Update 2016/08/10 since I need an anterior brain, and this blog is it, I'm now making this post my "living powershell" document.  I'll update it as I run into problems.  Today: $a vs "$a" vs $ (see end)

Mike Fal, a Powershell DBA extraordinaire, would probably shake his head at these.  But given the hard lessons learned this week, I thought I'd share.  I had to rewrite a bash script in Powershell, and some of the things I hadn't done before, so there was definitely a learning curve. And we'll see how the error trapping is - Idera has a webinar covering error-handling in Powershell next week.


First up, a basic powershell function that writes to a file. The encoding isn't strictly necessary here, but I use the code elsewhere.  For whatever reason, standard out-file adds a couple of low-ascii characters at the beginning of the file, which other utilities such as psftp choke on.    (  Doesn't matter here, but later I write to a file that I then feed to psftp as a batchfile. 

"mget TESTAREA/$FL" |out-file -append -encoding ascii $CMDS 
 I should just have the function accept a parameter.  And for whatever reason, when it writes to the log, it doesn't include newlines.

#Write whatever is in "$logmsg" to the log file $LOGFILE
function logstamp(){
    $loginfo = get-date -format "yyyyMMdd.HH:mm:ss"  #MM is MONTH, mm is minute
    "$loginfo $logmsg" |out-file -append -encoding ascii $logfile
    $logmsg="" }


I needed to parse out a Unix filename from a file.  The original "ls" looked like this:

-rw-rw-r-- 1 psabcderfg data 646621 Jul 19 16:25 myfile16071901.Z

The unix command to parse it would
cat $fl |awk '{print $9}'

And the powershell equivalent is:
$newfiles = get-content $fl | foreach {$_.split(" ")[8]}
(the number is 8 instead of 9 because powershell arrays start at 0)
(2016/09/07 but I've run into times when it IS 9.  WTF?)

A basic unix2dos-style file change. Found at

get-content $thisfile |% {$_.replace("`n", "`r`n")} | out-file -encoding ascii -filepath $thatfile -Force

Since it outputs to a new file, the timestamps won't match, but you could use set-filetimestamps -doc $doc -date $date to change them.


This next part is weird but also very handy.  Because of the way Powershell handles messages returned from applications, as well as standard error, you have to do use a trick to get out all the messages.  I needed this because I wanted all the messages from the application, 7zip here, but also stuff like psftp.

$logmsg = cmd /c 7za.exe e -aoa $thisfile 2>&1 | %{ "$_" }
    if ($LASTEXITCODE -ne 0)
         { logstamp; $logmsg = "7-zip Command to extract $thisfile returned exit code $LASTEXITCODE"; logstamp; throw "7-zip Command to extract $thisfile returned exit code $LASTEXITCODE" }
    else { logstamp; $logmsg = "7-zip Command to extract $thisfile finished successfully"; logstamp }

First, we're invoking it with cmd instead of start-process or just a dot invocation.  That's to make sure we get the messages out, but also to prevent powershell from choking if the app throws an error.  Next up is getting all the messages out from the app and stderr, which is the code from "2>&1" until the end of the line.

So now, we have a couple different things to look at.  $logmsg contains the entirety, both error messages from 7zip, and standard error.

$LASTEXITCODE returns the result code from the APP CALLED.  When it couldn't find the file to zip, $lastexitcode returned was 1.  Otherwise was 0 (success).

Next is $error[0].TargetObject, which I believe is the stderr from the app.

Was running into a problem invoking cpio.  I was having to make a unix-formatted file, and the way I'd check is by opening it in notepad.  Except...

$ldclist = get-childitem * -include *out.Z

I wanted to write those out to a file.  So I thought I'd do this to test:

That does indeed give me the directory info on each file.

But I just want the name of each file and didn't think about $myvarname.myfieldname.
So I ran this:
"$ldclist"|out-file -append -encoding ascii listoffiles

Which, superficially looks right.  My script auto-converted it to unix-format, and then when I open it in notepad, it's all on one line.  Denoting LFs, right?  Nope.  Using "$myvar" means it returns it as a list, space-separated.

Just to make it more fun, let's try this:
    $|out-file -append -encoding ascii a
    "$"|out-file -append -encoding ascii b

Those should be close, right?  We now know one would return the space-separated list (the quotes converting it to a string, IIRC), and the other the correct list?  Nope.  The first one returns just the base filenames.  The second one, in the quote, returns the full filename including path, space-separated.


Additionally, problems arise when trying to do something like this:
$logmsg = cmd/c 7za.exe -e -aoa $thisfile -o"$newdirectory"

It just doesn't work.  It actually dumped them into a folder called $newdirectory.
I wound up having to do the below, though I still couldn't use double-quotes. I know there's gotta be a way, but at this point I give up.
$command = "7za.exe -e -aoa $thisfile -o$newdirectory"
$logmsg = cmd /c $command 2>&1 | %{ "$_" }

2016/09/29 hey, a new installment! I have a ton more ,but this one drove me up a wall today, so here we go.

This time - powershell helping with small arrays.
I was trying to make sure I can hit a server and pull back some server info.
One thing I wanted to look at was the number of drives the server had.  Which worked great - on some servers.  On the others, blank.  Not 0, just... blank.

Here's the command I was running:
$drives = (gwmi -cn $compname -Class win32_logicaldisk -ErrorAction silentlycontinue | where {$_.Size -gt 0}).count

What's different?  Well, some of the servers only have 1 drive.
Turns out, if there's only 0 or 1 members in what's returned, it assumes it doesn't need to be an array.  Which means that count, for instance, won't work on it properly.

$drives = gwmi -cn $compname -Class win32_logicaldisk -ErrorAction silentlycontinue | where {$_.Size -gt 0}|measure

So the "measure" counts the number of items, even if it's only 1.  
Then when I query it, I use $drives.count, which gives me the proper number/.

No comments: