Introduction
This article integrates PowerShell with MFC, making available to MFC applications, about 90% of PowerShell powerful processing capability.
Background
Beginner's MFC background knowledge is required. Of course, PowerShell knowledge is very desirable.
Using the code
The idea is simple: I design a MFC class called CPowerShell, and using one instance of it, I call a c++ virtual property called Run, specifying the PowerShell script to run. For instance:
CPowerShell posh;
posh.Run = _T("dir");
or
CPowerShell posh;
posh.AssociatedListBox=&m_listBox; // if you want the listbox control represented by that variable be automatically filled in
posh.AssociatedEdit=&m_editBox; // if you want the editbox control represented by that variable be automatically filled in
posh.Run = _T("dir");
That's all! It's very simple indeed!
Please, notice that the dir command used above is an alias for the PowerShell cmdlet Get-ChildItem, and not the homonym from the command line commands.
Yes, I created a new verb to write the title: I PowerShell, he/she/it and MFC PowerShells (easily). Very soon, everyone will be PowerShelling. It is very powerful and very useful for non IT people too.
Therefore, I decided to create a MFC class, as simple as possible, to drive PowerShell, using registered PowerShell COM objects. This was the first problem I met - I did not find any PowerShell COM object registered in my computer! So, I went back to read about_powershell.exe, the documentation for the PowerShell executable, and I found what I need: a parameter (-command) that accepts as argument a script (a string too). It's all I needed.
Conclusion: Can't I use COM technology? No problem! I see I can use CROM technology!
Using CROM technology is almost like using COM, and we can still borrow the CROM/COM objects implemented by others. In the case of CROM/PowerShell, it's possible to run the executable with specific argument to have PowerShell executing some task, and possibly, returning some results. The task can be either simple, complex or very complex, with remoting or not.
Writing this article, I got very creative. After creating a new verb (to PowerShell), I created a new acronym: CROM - Creatable (through CreateProcess API) and Re-creatable (again, through CreateProcess API) Object Model. Initially, I considered calling it Smart Object (SOB), but its acronym has already a specific meaning... So, CROM is my choice, and it's a homonym for a fictional deity (I watched a movied called CROM, the barbarian or something similar...).
Before we continue, I must tell you that this article is about PowerShell, which, currently, means version 4. If you use PowerShell version 3, you may find some inconsistencies; just upgrade to version 4. Any version less than 3, is not PowerShell anymore; it is rubbishell, and probably most PowerShell codes/scripts in this article will fail.
I must tell you need this: Windows 8.1 (fully updated) and Visual Studio Community 2013 or equivalent (fully updated) are required.
After PowerShell runs the script (the one above or any other), the results can be automatically written into either a CListBox or a CEdit object (or into both) by the CPowerShell object. The results can be read from an array held by the CPowerShell object, anytime, until a new script is run.
By the example above, anyone can notice that I rather write down the alias instead of the cmdlet names, just to type much less keys. dir (3 keys) is much shorter than Get-ChildItem (13 keys plus shift key thrice). Nonetheless, in my opinion, no one should ever use an alias without remembering the respective cmdlet name, every time an alias is used. Moreover, when documenting a PowerShell code (script) the cmdlet name must always be used.
If you want to know the cmdlet corresponding to the alias dir, in the attached application window, type Ctrl+A, "gal dir" and Alt+R+R (or F5).
By now, you must be quite curious about the meaning of "c++ virtual property" expression I used some lines above. The reason, I have already explained: writing this article, I got very creative. In my conception, there are 2 types of "c++ properties": virtual and real. The "c++ virtual property" (without memory support), and the "c++ property" (which has memory support).
If you look in the file named PowerShell.h, you'll see macros related to these conceptions.
The CPowerShell MFC class is very simple, nevertheless, it's very powerful, just because PowerShell is very (really very) powerful, and I borrow all that power through the CROM (LOL) technology.
The CPowerShell MFC class implementation is basically instantiating powershell.exe executable with an argument specifying the -command parameter and the script to be run. Instead of using any IPC mechanism to send results back to the MFC desktop application, I use the simplest method: user's local TMP folder. That's it.
It's important to understanda key point about the CPowerShell MFC class: The script specified must make PowerShell return to its command prompt only after the task is fully executed and the output is fully created ( this makes Start-Job of no or very limited use ). The exception is for registered tasks and jobs, which open a new world of possibilities.
Where are the errors?
The CPowerShell is a simple MFC class, and it doesn't catch the error stream, automatically. Thus, if the user wants to see any errors, he/she must code the script to redirect the error stream to the success stream. For instance:
function f{
dir c:\Qwertytrewq
dir c:\Asdfgfdsa
dir c:\Windows *ini
}f
The script above will cause PowerShell to write error messages (I hope you don't have folders with names like the first 2) that won't be caught by the MFC CPowerShell object. But it's possible to change the code a little bit and redirect the error messages along with the success output:
function f{
dir c:\Qwertytrewq
dir c:\Asdfgfdsa
dir c:\Windows *ini
}f 2>&1
The attached project brings 15 examples. Right after the apllication is run, the edit view window shows information about 3 PowerShell automatic variables: $PSVersionTable
, $Host
and $MyInvocation. HEY! so there are 16 examples!
Notice that, at any time, you can enter your own PowerShell script, by pressing Ctrl+A (to clear the window) and type your script. To run it, press Alt+R+R, or just F5.
For instance, rigt now, type Ctrl+A and type gcm; now press Alt+R+R (or just F5) to see all commands (alias, functions and cmdlets) available in the PowerShell session.
Example #1: a very puzzling example to anyone not used to PowerShell.
This PowerShell script shows all xml items in 3 specific folders.
function ={
dir $args[1..(-1+$args.count)] $args[0]
}= *xml $pshome c:/windows c:/windows/system32
Example #2: your documents folder
dir ~/documents
Example #3: what's your IP
And some more info:
[Net.Dns]::gethostbyname($env:computername)|
tee -var var|
% addresslist
$var|
ft -a
Example #4: grouping items in a folder, by extension
dir $env:windir/* -include *dll,*exe,*ini|
sort extension|
ft -groupby extension
Example #5: full help, please
It's easy to show the local documentation for the Compare-Object cmdlet (diff):
help -full diff
Notice example 6.
Example #6: the example 6, from documentation mentioned above.
$procsBefore=ps
notepad.exe
$procsAfter=ps
diff -referenceobject $procsBefore -differenceobject $procsAfter
Example #7: just the names, nothing more
dir $env:windir/* -include *dll,*exe,*ini|
fw -column 1
Example #8: what can you tell me about this powershell.exe
gi $pshome/powershell.exe|
fl *
Example #9: Renaming files and COM objects
For this example, you need to have JS, VBS and BAT files in C:\Temp folder. The PowerShell script will rename all of them, by appending .TXT to each of them. Before renaming the files a popup window will ask you if you really want to rename them. If you press NO (or the popup windows times out), the files won't be renamed.
Notice, the popup window is "borrowed" from the COM object Wscript.Shell.
$obj=new-object -com wscript.shell
$r=$obj.popup('Do you want to rename the files? This dialog box will auto extinguish in 10s... ...so, be prepared.', 10, 'let me know:', 0x04 + 0x20)
$null=[system.runtime.interopservices.marshal]::releasecomobject($obj)
if($r-eq6){
write 'the files were renamed.'
dir c:/temp/* -inc *.JS,*.VBS,*.BAT|ren -new{"$_.txt"}
}else{
write 'the files were NOT renamed.'
}
Example #A: a very short script and a huge list of classes in the default CIM NameSpace (ROOT/cimv2)
gcls -class *
Example #B: some information from CIM_OperatingSystem class
$props='caption','status','installdate','lastbootuptime','muilanguages'
gcim -class CIM_OperatingSystem|
ft $props -a|
out-string -width 120
Example #C: time to read the TOP STORIES from CNN (invoking REST method)
write 'from CNN:' ''
$rss='http://rss.cnn.com/rss/edition.rss'
irm $rss|
ft tit*,desc* -a -wr|
out-string -width 140
Example #D: more news, this time in a window "borrowed" from PowerShell, through CROM (LOL) technology
Thanks to CROM technology, we can use the Out-GridView ( help -full ogv ) to show the stories from Daily Mirror.
$u='www.mirror.co.uk/news/rss.xml'
irm $u|
select title,@{l='description';e={$_.description.innertext}}|
ogv -title 'from Daily Mirror:' -output single|
fl
Example #E: JSON Objects and Earthquakes - how are they related
The default browser has the answer.
(wget http://www.seismi.org/api/eqs|ConvertFrom-Json).earthquakes|
select -first 20|
ConvertTo-Html -title EARTHQUAKES >($htm=$env:TMP+'/=~=quakes.htm')
ii $htm
I could have used Invoke-RestMethod (help -full irm) instead of Invoke-WebRequest (help -full wget), but using wget makes clear that JSON objects were downloaded and then, converted to psobjects.
Example #F: Thanks to CROM technology, all these are "borrowed/used" - YouTube videos API, PowerShell window through Out-GridView and .NET WebControl to play a video
CROM (LOL) technology, is really powerfull, thanks to the PowerShell objects (and others) it is able to access.
This last example, will query YouTube about "timelapse" videos and show 15 (at most) in an Out-GridView, for you, the user select one (if you cancel the selection, the script ends here). The video selected, will then, be played in a .NET WebBrowser control.
If you want to finish, you have no other way but to press Alt+F4.
$qry=@'
http://gdata.youtube.com/feeds/api/videos?
q=timelapse
&orderby=published
&max-results=15
&v=2
'@
irm ($qry-replace'\s')|
select title,@{
l='author'
e={$_.author.name}
},@{
l='date published'
e={$_.published -as [datetime]}
},@{
l='video id'
e={($_.id-split':')[-1]}
}|
ogv -title 'Timelapse videos' -output single|%{
$wb=[Windows.Controls.WebBrowser]@{
cliptobounds=$true
}
$wnd=[Windows.Window]@{
topmost=$true
windowstate='maximized'
windowstyle='none'
content=$wb
}
$wb.navigate("https://www.youtube.com/watch?v=$($_.'video id')")
$null=$wnd.showdialog()
}
Points of Interest
Due to the fact that the MFC CPowerShell class is unable to trap the outputs to the host, some cmdlets (notably those that contain 'host' in the noun) and features cannot be used with this MFC class.
Through PowerShell and this MFC class, any MFC application can aggregate a huge number of features without any code, thanks to CROM technology, using PowerShell features not yet mentioned, like registered tasks and jobs.
History
just posted
4/March/2015: included a new "C++ virtual property", RunAsync
, in the CPowerShell
MFC class.
5/March/2015: included function Delete()
inside BOOL RunScript(CString&)
function's every return statement, for cleaning up.
11/March/2015: I just changed the article section to MFC