Invoking PowerShell with complex expressions using Scriptblocks

<WIZARD WARNING>


First a reminder:  when you are in PowerShell (formerly knows as Monad), you can run anything you want out-of-process using the construct:


PowerShell {Scriptblock}


The great example of this is



PS> #RUN EVERYTHING IN PROCESS
PS> get-process |where {$_.handles -ge 900} |sort handles


Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
   1369      45    55328      16880   469    81.67   3056 OUTLOOK
   1575      48    84616     116200   450   103.52   3212 iexplore
   2367     190    31192      40676   152   179.70   1768 svchost



PS> #RUN EVERYTHING OUT OF PROCESS
PS> PowerShell {get-process |where {$_.handles -ge 900} |sort handles}


Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
   1369      45    55328      16880   469    81.73   3056 OUTLOOK
   1575      48    84616     116200   450   103.56   3212 iexplore
   2369     190    31192      40676   152   179.73   1768 svchost



PS> #RUN the first 2 elements of the pipeline in process and the last in-process
PS> PowerShell {get-process |where {$_.handles -ge 900}} |sort handles


Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
   1369      45    55328      16880   469    81.75   3056 OUTLOOK
   1571      48    84768     116352   450   103.59   3212 iexplore
   2373     190    30716      40220   152   179.81   1768 svchost



PS> #RUN the first element of the pipeline in process and the last 2 in-process
PS> PowerShell {get-process} |where {$_.handles -ge 900} |sort handles


Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
   1369      45    55328      16948   469    81.80   3056 OUTLOOK
   1567      48    84252     115892   449   103.63   3212 iexplore
   2398     190    31292      40724   153   180.02   1768 svchost


Now – have you ever tried to do that from CMD.EXE?


C:\>powershell {get-process |where {$_.handles -ge 900} | Sort Handles }
‘where’ is not recognized as an internal or external command,
operable program or batch file.


That doesn’t work so well.  You try a number of things and depending upon what you are trying to do, that will work better or worse.  If you want to execute script blocks with single and double quotes, this becomes incomprehensible pretty quickly. 


The problem is that there are various components which try to “help” you by processing the arguments (e.g. stripping off quotes).  The reason this ALWAYS works when you invoke it from Powershell is … we cheat.   Well, we cheat in a Kabayashi Maru sort of way (See Kirk’s solution http://en.wikipedia.org/wiki/Kobayashi_Maru). 


Here is a trick that you can use to see what is going on.  The system type [ENVIRONMENT] has lots of great static properties:



PS> [Environment] |gm -static -membertype property



   TypeName: System.Environment


Name               MemberType Definition
—-               ———- ———-
CommandLine        Property   static System.String CommandLine {get;}
CurrentDirectory   Property   static System.String CurrentDirectory {get…
ExitCode           Property   static System.Int32 ExitCode {get;set;}
HasShutdownStarted Property   static System.Boolean HasShutdownStarted {…
MachineName        Property   static System.String MachineName {get;}
NewLine            Property   static System.String NewLine {get;}
OSVersion          Property   static System.OperatingSystem OSVersion {g…
ProcessorCount     Property   static System.Int32 ProcessorCount {get;}
StackTrace         Property   static System.String StackTrace {get;}
SystemDirectory    Property   static System.String SystemDirectory {get;}
TickCount          Property   static System.Int32 TickCount {get;}
UserDomainName     Property   static System.String UserDomainName {get;}
UserInteractive    Property   static System.Boolean UserInteractive {get;}
UserName           Property   static System.String UserName {get;}
Version            Property   static System.Version Version {get;}
WorkingSet         Property   static System.Int64 WorkingSet {get;}


Including the commandline used to execute the current process.


PS> [Environment]::commandline
“C:\Program Files\Windows PowerShell\v1.0\powershell.exe”


From there, you can do the following to see our cheat:


PS> PowerShell {[Environment]::CommandLine}
“C:\Program Files\Windows PowerShell\v1.0\powershell.exe” -encodedCommand WwBFAG4AdgBpAHIAbwBuAG0AZQBuAHQAXQA6ADoAQwBvAG0AbQBhAG4AZABMAGkAbgBlAA== -inputFormat xml -outputFormat xml


When we launch an external process which has a parameter with {}’s we say that that is a subshell and we TWEAK the command invocation by Base64 encoding the scriptblock and by telling the command to consume and produce XML.


If you ever need to invoke a complex piece of code directly from CMD.exe you can run PowerShell and do the following to get the string to pass to -EncodedCommand :


PS> $script = {gps |where {$_.handles -ge 900}|sort handles}.ToString()
PS> [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetByte
s($script))
ZwBwAHMAIAB8AHcAaABlAHIAZQAgAHsAJABfAC4AaABhAG4AZABsAGUAcwAgAC0AZwBlACAAOQA
wADAAfQB8AHMAbwByAHQAIABoAGEAbgBkAGwAZQBzAA==


You can then use that string from CMD.EXE:


C:\>PowerShell -EncodedCommand ZwBwAHMAIAB8AHcAaABlAHIAZQAgAHsAJABfAC4AaABhAG4AZ
ABsAGUAcwAgAC0AZwBlACAAOQAwADAAfQB8AHMAbwByAHQAIABoAGEAbgBkAGwAZQBzAA==


Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
   1366      45    55328      21552   469    83.50   3056 OUTLOOK
   1665      48    90164     122736   466   276.13   3212 iexplore
   2396     202    31036      40664   153   192.08   1768 svchost


Enjoy!
Jeffrey Snover [MSFT]
Windows PowerShell Architect


 


PSMDTAG:FAQ: What is the -EncodedCommand parameter on PowerShell.Exe for?
PSMDTAG:FAQ: How can I see the command line that started the PowerShell process?
PSMDTAG:FAQ: How do I run a scriptblock in a seperate process?
PSMDTAG:INTERNAL: avoiding funky syntax across process invocations, Base64 encoding of ENCODEDCOMMAND