Cutting Edge Silverlight4 COM+ Features

Justin Angel picture

Justin
Angel

Hi Folks,

The new Silverlight 4 COM+ Automation feature can be used to perform a myriad of previously unavailable tasks in Silverlight.
In this blog post I’ll review some of these tasks, what COM+ Automation is, some related issues and helpful introduce developer tools.

 

The code we’ve created in this blog post is available @ http://Justinangel.net/Storage/SL4Com/SL4Com.zip
And a Live demo is available @ http://Justinangel.net/Storage/SL4Com/testpage.html 

 

What is Silverlight 4 COM+ Automation support?

Silverlight 4 Beta 1 offers the ability to initialize and use COM+ classes from Silverlight.
It should be noted that these features are only available in elevated privileges Out-Of-Browser mode and are not available in-browser.  
In Silverlight 4 Beta1 Com+ Automation only works on Windows machines (but more on that later).

The majority of work around Silverlight 4 COM+ support is centered around the ComAutomationFactory and ComAutomationEvent classes.

 

Nooo! COM+ killed my inner child back in the 90s!

Man freaking out having been told he must go back to using COM+

Get over it.

Silverlight 4 COM+ Automation is not about authoring, deploying or versioning COM+ components, that is a worst practice for this feature.  
The best practice for Silverlight 4 COM+ is to only use common operating system COM+ classes, and not ship your own.

Given that best practice, you are just the consumer of some time tested Windows APIs.
Using COM+ Windows APIs is no different then consuming the .Net framework, 3rd party frameworks or p/Invoke features. 

 

Stop yapping and show me how to setup a Silverlight 4 OOB project!

We’ll start by creating a new Silverlight 4 OOB (Out Of Browser) project with Elevated Privileges in Visual Studio 2010. 

New Silverlight project

For our test project we don’t need a Server-Side project, and we need to make sure we’re using Silverlight 4.

Selecting Silverlight 4 project

Right-Click on the SL4Com project and choose “Properties”.
Check the “Enable Running Application out of browser”.

Enable Running Application out of browser checkbox

Click “Out of Browser Settings” and set “Require Elevated Trust when running outside the browser”.

Require Elevated Trust when running outside the browser checkbox

Next add a Button to MainPage that’ll install our Out-Of-Browser application.

<Button x:Name="btnInstall" Click="btnInstall_Click" Content="Install Out-Of-Browser App" />

private void btnInstall_Click(object sender, RoutedEventArgs e)

{

    if (Application.Current.InstallState == InstallState.NotInstalled)

        Application.Current.Install();

}

Note: This article is about COM+, not best practices for wiring up events.
If this was a real application, we should have used Prism Commands or Blend Behaviours to hook up these events.

We’ll run our app and install it.

Install OOB button

Security Warning when installing OOB app

Next, we’ll need to tell Visual Studio 2010 to run the Out-Of-Browser app when debugging.

Go to Project properties –> Debug –> Installed out of browser application –> SL4Com.

Select debugging OOB SL4 Apps

One last step we have to do is add a reference to Microsoft.CSharp.dll so we could use the new Silverlight 4 dynamic keyword.

Adding a reference to Microsoft.CSharp.dll

(on my machine the DLL is at c:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\Microsoft.CSharp.dll)

 

Feature #1: Write files anywhere on the local machine

Using the FileSystemObject we have virtually unlimited access to the user’s file storage.

<Button x:Name="btnWriteFile" Content="Write file to C:\test.txt" Click="btnWriteFile_Click" />

private void btnWriteFile_Click(object sender, RoutedEventArgs e)

{

    using(dynamic fsoCom = ComAutomationFactory.CreateObject("Scripting.FileSystemObject"))

    {

        dynamic file = fsoCom.CreateTextFile(@"c:\test.txt", true);

        file.WriteLine("Bloody Hell!");

        file.WriteLine("Silverlight is writing to C:\\");

        file.Close();

    }

}

When we run our app and click the button we can see that a new file has been created on C:\test.txt.

Clcking the Write File button adds a new file to C:\Test.txt

 

Feature #2: Reading any file from the user’s machine

Using FileSystemObject.ReadAll method we can read any file off the user’s machine.

<Button x:Name="btnReadFile" Content="Read file C:\test.txt" Click="btnReadFile_Click" />

private void btnReadFile_Click(object sender, RoutedEventArgs e)

{

    using (dynamic fsoCom = ComAutomationFactory.CreateObject("Scripting.FileSystemObject"))

    {

        dynamic file = fsoCom.OpenTextFile(@"c:\test.txt", 1, true);

        MessageBox.Show(file.ReadAll());

        file.Close();

    }

}

Running the app and clicking the button shows us the file contents:

Clicking the read file button shows the file contents with a MessageBox

 

What else can FileSystemObject do for us?

The FSO object let’s us essentially own the user machine.
It provides a way to read, write, delete or modify any file or directory on the user’s file system.

Here’s a list of Methods you can explore independently:

 

Feature #3: Execute/Run any command or file

Using the WShell Run Method we can execute any command.

<Button x:Name="btnExecuteCmdPing" Content="Cmd Ping 127.0.0.1" Click="btnExecuteCmdPing_Click" />

private void btnExecuteCmdPing_Click(object sender, RoutedEventArgs e)

{

    using(dynamic shell = ComAutomationFactory.CreateObject("WScript.Shell"))

    {

        shell.Run(@"cmd /k ping 127.0.0.1");

    }

}

Clicking the button opens up the CMD and runs the specified command:

Clicking the CMD button executes the ping command

 

Feature #4: Emulate user input

Using the WShell SendKeys method we can send input to the currently focused application.
This feature is extremely useful for integration testing.

<Button x:Name="btnExecuteNotepadAndSendKeys" Content="Run Notepad and write something" Click="btnExecuteNotepadAndSendKeys_Click" />

private void btnExecuteNotepadAndSendKeys_Click(object sender, RoutedEventArgs e)

{

    using (dynamic shell = ComAutomationFactory.CreateObject("WScript.Shell"))

    {

        shell.Run(@"c:\windows\notepad.exe");

        shell.SendKeys("Justin Angel{Enter}Rocks!");

    }

}

Looking at the SendKeys syntax it’s easy to see there are special keys (like Enter, Tab, escape, ctrl, etc) that can be specified using a special notation.

Notepad with the ShowKeys text

 

Feature #5: Pin files to the Windows 7 Taskbar

Using The Shell.Application class we can iterate over all applications, check their Verbs and execute those verbs accordingly.
Here are the Verbs for the Calculator application:

image

However, this method allows us to Pin Calculator to the Win7 taskbar even when the Calculator is closed.
It does that by checking the Windows verbs on the LNK (Link) file to Calculator.

<Button x:Name="btnPinCalculator" Content="Pin Calcuator to the Win7 Taskbar" Click="btnPinCalculator_Click" />

private void btnPinCalculator_Click(object sender, RoutedEventArgs e)

{

    using (dynamic ShellApplication = ComAutomationFactory.CreateObject("Shell.Application"))

    {

        dynamic commonPrograms = ShellApplication.NameSpace(23);

        string allUsersPath = commonPrograms.Self.Path;

 

        dynamic directory = ShellApplication.NameSpace(allUsersPath + @"\Accessories");

        dynamic link = directory.ParseName("Calculator.lnk");

 

        dynamic verbs = link.Verbs();

        for (int i = 0; i < verbs.Count(); i++)

        {

            dynamic verb = verbs.Item(i);

            if (verb.Name.Replace(@"&", string.Empty).ToLower() == "pin to taskbar")

            {

                verb.DoIt();

            }

        }

    }

}

Essentially, what this method does is:
1. Gets the path for all the users start menu links.
2. Gets the specific link for Calculator.
3. Checks what Verbs are available for that link.
4. Execute the “Pin to Taskbar” verb.

And when running this application we can see Calculator is now pinned to the user’s taskbar:

Calculator Pinned to the Win7 Taskbar

 

Feature #6: Read any Registry values

Using the Shell.Application RegRed method you can read values from the machine’s registry.

At the "HKLM\Software\Microsoft\Windows\CurrentVersion\CommonFilesDir" registry key we can see the location of the machine’s Common Files:

RegEdit for HKLM\Software\Microsoft\Windows\CurrentVersion\CommonFilesDir

We can easily access that value using RegRead.

<Button x:Name="btnReadRegistry" Content="Read HKLM registry" Click="btnReadRegistry_Click" />

private void btnReadRegistry_Click(object sender, RoutedEventArgs e)

{

    using (dynamic WShell = ComAutomationFactory.CreateObject("WScript.Shell"))

    {

        string reg = WShell.RegRead(@"HKLM\Software\Microsoft\Windows\CurrentVersion\CommonFilesDir");

        MessageBox.Show("The Program files on this machine are at: " + reg);

    }

}

Clicking the button shows the value of the registry key.

Clicking the read registry button shows the location of the Program Files directory

Feature #7: Add OOB App to Windows’ Startup

Using a combination of the LNK technique we’ve seen earlier and Shell.Application RegWrite method we can add our OOB app to the Windows’ startup applications.
Every program listed under “HKLM\Software\Microsoft\Windows\CurrentVersion\Run\” will be executed when the machine first starts up.
So if we can get our OOB link and add it to that Registry location, we can add our OOB application to Windows’ Startup.

HKLM\Software\Microsoft\Windows\CurrentVersion\Run\

 

<Button x:Name="btnAddOOBAppToStartup" Content="Add out-of-browser application to Startup" Click="btnAddOOBAppToStartup_Click" />

private void btnAddOOBAppToStartup_Click(object sender, RoutedEventArgs e)

{

    using (dynamic ShellApplication = ComAutomationFactory.CreateObject("Shell.Application"))

    {

        dynamic commonPrograms = ShellApplication.NameSpace(11);

        string allUsersPath = commonPrograms.Self.Path;

 

        dynamic directory = ShellApplication.NameSpace(allUsersPath + @"\Programs");

        dynamic link = directory.ParseName(Deployment.Current.OutOfBrowserSettings.ShortName + ".lnk");

        string OOBLink = link.Path;

 

        using (dynamic WShell = ComAutomationFactory.CreateObject("WScript.Shell"))

        {

           WShell.RegWrite(@"HKLM\Software\Microsoft\Windows\CurrentVersion\Run\"

                                        + Deployment.Current.OutOfBrowserSettings.ShortName,

                                        OOBLink);

            MessageBox.Show("Please restart your machine and this application will load on startup.");

        }

    }

}

This code snippet isn’t all that complicated.
First, we get the LNK to the current out-of-browser application by checking the start menu links directory.
Next, we add that LNK file Uri to the registry node we mentioned earlier on.

And after running clicking the button we can see the registry value was added:

SLCOM application Startup registry entry

 

Task #8: Pinning the OOB Application to Windows’ 7 Taskbar

We’ve previously seen how to get the LNK for the OOB application and we’ve seen how to pin those LNKs to the Taskbar.
It’s fairly easy to combine those two techniques to pin the current OOB app to the taskbar.

<Button x:Name="btnPinOOBApp" Content="Pin Out-of-Browser application to Taskbar" Click="btnPinOOBApp_Click" />

private void btnPinOOBApp_Click(object sender, RoutedEventArgs e)

{

    using (dynamic ShellApplication = ComAutomationFactory.CreateObject("Shell.Application"))

    {

        dynamic commonPrograms = ShellApplication.NameSpace(11);

        string allUsersPath = commonPrograms.Self.Path;

 

        dynamic directory = ShellApplication.NameSpace(allUsersPath + @"\Programs");

        dynamic link = directory.ParseName(Deployment.Current.OutOfBrowserSettings.ShortName + ".lnk");

 

        dynamic verbs = link.Verbs();

        for (int i = 0; i < verbs.Count(); i++)

        {

            dynamic verb = verbs.Item(i);

            if (verb.Name.Replace(@"&", string.Empty).ToLower() == "pin to taskbar")

            {

                verb.DoIt();

                MessageBox.Show("Close this application and start it up again from Win7 Taskbar");

            }

        }

    }

}

And we can see that even when our OOB application is closed, it’s still showing up on the Win7 taskbar.

SL4 Com application is closed but still found on the Win7 Taskbar

 

Feature #9: Text to Speech

Using Windows’ built-in SAPI engine we can easily do Text to Speech and even Speech to Text.

We’ll demo using the SAPI.SpVoice class for Text2Speech. 
Feel free to explore the SAPI namespace since it’s full of other useful speech related features.

<Button x:Name="btnText2Speech" Content="Use Text to Speech" Click="btnText2Speech_Click" />

private void btnText2Speech_Click(object sender, RoutedEventArgs e)

{

    using (dynamic ISpeechVoice = ComAutomationFactory.CreateObject("SAPI.SpVoice"))

    {

        ISpeechVoice.Volume = 100;

        ISpeechVoice.Speak("<rate speed=\"0\"><pitch middle=\"0\">Hello World! My HoverCraft is full of eels.");

    }

}

Clicking the button does indeed cause Windows to start speaking.
For a relatively acceptable use of this feature, have a look at my Molecular Biology DNA Visualizer.

It is also possible to do limited speech recognition using SAPI using the SAPI.SpInprocRecognizer class.

 

Feature #10: Execute SQL with Local Databases

Using ODBC features available in Windows, we can easily connect to local databases and execute SQL.

Here’s a sample of executing a simple SQL Insert with the local Northwind database:

<Button x:Name="btnExecuteSQL" Content="Execute SQL Insert statement" Click="btnExecuteSQL_Click" />

using (dynamic IDbConnection = ComAutomationFactory.CreateObject("ADODB.Connection"))

using (dynamic IDbCommand = ComAutomationFactory.CreateObject("ADODB.Command"))

{

    IDbConnection.ConnectionString = "driver={SQL Server};" +

                                    "server=.\\;uid=sa;pwd=password;database=Northwind";

 

    IDbConnection.Open();

    IDbCommand.ActiveConnection = IDbConnection;

 

    IDbCommand.CommandText =

        @"INSERT INTO [Northwind].[dbo].[Region]

                        ([RegionID], [RegionDescription]) VALUES (10, 'JustinLand')";

 

    IDbCommand.Execute();

}

Note: this code snippet does not show best practices for using ODBC for Silverlight. For one thing, Commands should use parameters and not strings.

 

If you were to execute this code sample you’d probably get the following error:

SQL connection exception

Since you don’t have the Northwind Database installed or the username & password are wrong you’d see some exception or another.
Allow me to assure you “It works on my machine”!

The JustinLand record inserted into a local Database

It is important to discuss best practices at this point.
It is not my intent to advocate executing local SQL statements in conjunction with a local database.

However, It is possible to convert any 3rd party OR/M or Persistence layer to run on ODBC.
Which would make for a very comfortable EF/Linq2SQl/NH like syntax. In that case, it would be much more acceptable to use ODBC.

Another issue with this method is the end-user might not have any local database available.
And you’ll end up resorting using Access or the likes.

Personally I believe that for local Silverlight storage we should use local OODBs, and not use ODBC.

 

Feature #11: Automate Scanners and Cameras

Using the WIA (Windows Image Acquisition) infrastructure we can automate external Scanners, Cameras and Video cameras.
Many very unique samples (converting file formats, getting scanners to scan, taking pictures, etc) are available at the WIA Samples page.

One possible task is to iterate over all Cameras and Scanners currently connected to the local machine and their commands:

<Button x:Name="btnIterateWIA" Content="Iterate over external Cameras and Scanners" Click="btnIterateWIA_Click" />

private void btnIterateWIA_Click(object sender, RoutedEventArgs e)

{

    StringBuilder sb = new StringBuilder();

    sb.AppendLine("List of available external devices and commands:");

    using (dynamic DeviceManager = ComAutomationFactory.CreateObject("WIA.DeviceManager"))

    {

        var deviceInfos = DeviceManager.DeviceInfos;

        for (int i = 1; i <= deviceInfos.Count; i++)

        {

            var IDevice = deviceInfos.Item(i).Connect();

            var DeviceID = IDevice.DeviceID;

            var DeviceName = IDevice.Properties("Name").Value;

            var Commands = IDevice.Commands;

            for (int j = 1; j <= Commands.Count; j++)

            {

                var IDeviceCommand = Commands.Item(j);

                var CommandName = IDeviceCommand.Name;

                var CommandDescription = IDeviceCommand.Description;

                sb.AppendLine("    " + DeviceName + " (" + DeviceID + "), " + CommandName + ": " + CommandDescription);

                // Execute with: IDevice.ExecuteCommand(IDeviceCommand.CommandID);

            }

        }

    }

    MessageBox.Show(sb.ToString());

}

The above code is actually pretty simple.
1. We initialize a DeviceManager.
2. We ask for all the DeviceInfos.
3. We then iterate over all devices, getting their commands.
4. We display the command name and device name.

On my dev machine with an External Scanner and External Camera, this is what we see:

List of device commands

Another thing we can do is select Images from external Image devices.

<Button x:Name="btnGetExternalImage" Content="Acquire External Image from Scanner/Camera" Click="btnGetExternalImage_Click" />

private void btnGetExternalImage_Click(object sender, RoutedEventArgs e)

{

    using (dynamic CommonDialog = ComAutomationFactory.CreateObject("WIA.CommonDialog"))

    {

        dynamic imageFile = CommonDialog.ShowAcquireImage();

        if (imageFile != null)

        {

            imageFile.SaveFile(@"c:\myImage.jpg");

            MessageBox.Show("Saved c:\\myImage.jpg");

        }

    }

}

When clicking the button on my local machine we’ll see the following dialogs:

Device Selection Dialog

Select Image Dialog

And we can see c:\myImage.jpg is indeed that same photo:

c:\myImage.jpg print screen 

 

 

Feature #13: Use the Windows 7 Location API

As part of Windows 7 the Location API was added to provide developers with the current machine GPS location.
If the proper hardware is connect, Using the Location API we can get the current Latitude and Longitude of the current Machine.

<Button x:Name="btnWin7LocationAPI" Content="Activate Windows 7 Location API" Click="btnWin7LocationAPI_Click" />

private void btnWin7LocationAPI_Click(object sender, RoutedEventArgs e)

{

    using (dynamic LatLongReportFactory = ComAutomationFactory.CreateObject("LocationDisp.LatLongReportFactory"))

    {

        uint curStatus = LatLongReportFactory.Status;

        if (curStatus == (uint)4)

        {

            using (dynamic LatLongReport = LatLongReportFactory.LatLongReport)

            {

                MessageBox.Show("Latitude: " + LatLongReportFactory.Latitude + "\r\n Longitude: " + LatLongReportFactory.Longitude);

            }

        }

 

        LatLongReportFactory.ListenForReports(5000);

        ComAutomationEvent NewLatLongReportEvent = ComAutomationFactory.GetEvent(LatLongReportFactory, "NewLatLongReport");

        NewLatLongReportEvent.EventRaised += (s, args) =>

        {

            using (dynamic LatLongReport = LatLongReportFactory.LatLongReport)

            {

                MessageBox.Show("Latitude: " + LatLongReportFactory.Latitude + "\r\n Longitude: " + LatLongReportFactory.Longitude);

            }

            LatLongReportFactory.StopListeningForReports();

        };

    }

}

The above code will display the current Latitude and longitude and then listen in for new reports every 5 seconds.

 

Feature #14: Use Classes from the Full .Net framework

While it is not recommended, you can consume some classes from the full .Net framework.
The reason it is not recommended, is because all of your users would need to have the full .Net framework installed.

Here’s an example of initializing the ReaderWriterLock class used for multithreading multiple readers or a single writer.

<Button x:Name="btnUseFullDotNet" Content="Use a class from the full .Net frameowrk" Click="btnUseFullDotNet_Click"/>

private static int foo = 0;

private void btnUseFullDotNet_Click(object sender, RoutedEventArgs e)

{

    using (dynamic ReaderWriterLock = ComAutomationFactory.CreateObject("System.Threading.ReaderWriterLock"))

    {

        ReaderWriterLock.AcquireWriterLock(1);

        MessageBox.Show("System.Threading.ReaderWriterLock.AcquireWriterLock() - acquired lock");

        foo++;

        ReaderWriterLock.ReleaseWriterLock();

        MessageBox.Show("System.Threading.ReaderWriterLock.ReleaseWriterLock() - released lock");

        ReaderWriterLock.AcquireReaderLock(1);

        MessageBox.Show("While in reader lock - Foo value is " + foo);

        ReaderWriterLock.ReleaseReaderLock();

 

    }

}

Executing this code we’ll see the expected messages.

The interesting part here is that we’re using classes from the Full desktop .Net framework which are not available in Silverlight.

 

Feature #15: Use WMI to build FileSystemWatcher

Using the WbemScripting.SWbemLocator class we can execute WMI queries in Silverlight.
WMI allows querying for many operating system related information, like: Performance (CPU & Memory), Security, Networking, Storage, Kernel, Active Directory, etc.

In this sample we’ll rebuild the .Net FileSystemWatcher using WMI Events

<Button x:Name="btnActivateFileSystemWatcher" Content="Active FileSystemWatcher on C:\" Click="btnActivateFileSystemWatcher_Click" />

 

private void btnActivateFileSystemWatcher_Click(object sender, RoutedEventArgs e)

{

    new Thread(() =>

    {

        using (dynamic SWbemLocator = ComAutomationFactory.CreateObject("WbemScripting.SWbemLocator"))

        {

            SWbemLocator.Security_.ImpersonationLevel = 3;

            SWbemLocator.Security_.AuthenticationLevel = 4;

            dynamic IService = SWbemLocator.ConnectServer(".", @"root\cimv2");

 

            string fileSystemWatcherQuery =

                @"SELECT * FROM __InstanceOperationEvent WITHIN 3 WHERE Targetinstance ISA 'CIM_DirectoryContainsFile' and TargetInstance.GroupComponent= 'Win32_Directory.Name=""c:\\\\""'";

            dynamic monitor = IService.ExecNotificationQuery(fileSystemWatcherQuery);

 

            Dispatcher.BeginInvoke(() => MessageBox.Show(@"Now listening to file changes on c:\"));

 

            while (true)

            {

                dynamic EventObject = monitor.NextEvent();

                string eventType = EventObject.Path_.Class;

                string path = EventObject.TargetInstance.PartComponent;

                Dispatcher.BeginInvoke(() =>  MessageBox.Show(eventType + ": " + path));

            }

        }

    }).Start();

}

This code might look complex, but it really isn’t.
1. We’ve initialized a WbemScripting.SWbemLocator and connected to the local Machine with proper security.
2. We’ve executed a WMI Query for all storage changes on C:\.
3. When that event fired we’ve shown a messagebox.

Since Wbem doesn’t directly support events, we have to constantly query it. So we’re executing the whole thing in a background thread.

Executing this code and adding c:\foo.txt file will cause the following messagebox to occur:

__InstanceCreationEvent: \\JITDEV\root\cimv2:CIM_DataFile.Name="c:\\foo.txt"

 

Feature #16: Iterate over valid ProgIDs

This one is just a shout-out to the COM infrastructure.
If you’ve been paying attention you’ve noticed some magic strings when calling ComAutomationFactory.CreateObject.

Those magic strings are the ProgIDs of registered COM+ components.
We can get a list of all valid ProgIDs using WMI.

<Button x:Name="btnIterateOverProgIDs" Content="Iterate over valid COM+ ProgIDs" Click="btnIterateOverProgIDs_Click" />

private void btnIterateOverProgIDs_Click(object sender, RoutedEventArgs e)

{

    StringBuilder sb = new StringBuilder();

    sb.AppendLine("The first 25 valid ProgIDs: ");

    using (dynamic SWbemLocator = ComAutomationFactory.CreateObject("WbemScripting.SWbemLocator"))

    {

        SWbemLocator.Security_.ImpersonationLevel = 3;

        SWbemLocator.Security_.AuthenticationLevel = 4;

        dynamic IService = SWbemLocator.ConnectServer(".", @"root\cimv2");

        dynamic QueryResults = IService.ExecQuery(

            @"SELECT Caption, Description, InprocServer32, ProgID FROM Win32_ClassicCOMClassSetting where progid is not null");

        for (int i = 1; i < 25 /*QueryResults.Count*/; i++)

        {

            string progID = QueryResults.ItemIndex(i).ProgID;

            sb.AppendLine("   " + progID);

        }

    }

    MessageBox.Show(sb.ToString());

}

After clicking the button we can see this list of the first 25 valid Classes for Com+ Automation:

image

 

Feature #17: Automate Microsoft Office

Office exposes a set of COM+ automation classes allowing to automate PowerPoint, word, excel, outlook, etc.
There’s been quite a lot written on this one so It’ll suffice to provide links to what’s already been said by folks more knowledgeable than myself:

Silverlight 4's New COM Automation Support by Jeff Prosise (Outlook)

Silverlight 4 Rough Notes: Trusted Applications by Mike Taulty (Excel)

Silverlight 4: New features overview (Part 3) – Elevated Out-of-browser applications by Alex Golesh (Word)

Silverlight TV Episode 1: The Joys of Trusted Out-of-Browser Applications by Keith Smith & John Papa (PowerPoint)


Tools for Silverlight 4 COM+ Development

When developing Silverlight COM+ applications these 4 tools will prove to be invaluable source of information.

 

Tool #1: WMI Explorer

WMI Explorer is a general purpose tool used to visualize WMI queries. (Which we’ve seen earlier)
Using WMI Explorer and the following query we can see a list of all available COM+ Components on the relevant machine:

SELECT Caption, Description, InprocServer32, ProgID FROM Win32_ClassicCOMClassSetting where progid is not null

WMI Explorer Print Screen 

This list is extremely helpful when examining which ProgIDs are available for COM Automation.

 

Tool #2: DLL Export Viewer

When working with dynamic types there’s no intellisense available.
But fear not, using DLL Export Viewer we essentially get the same information reflector would have provided us on an assembly.

First, find the COM+ DLL file address from WMI Explorer and load it into DLL Export Viewer:

Load DLL Dialog

Make sure “Scan COM Type Libraries” is checked.

And now we can see an exported list of members from the ODBC DLL:

DLL Export View for ADO ODBC

 

Tool #3: Resharper

Though development with dynamic types doesn’t offer intellisense, Resharper 5 makes this a little better by offering intellisense on members that have been previously used.

Resharper Intellisense for Run and SendKeys after being used

Notice the intellisense for the “Run” and “SendKeys” methods.

 

Tool #4: MSDN and Search Engines

Most core Windows’ COM+ components have adequate documentation up on MSDN when searching for the type name on google:

Google Search results for MSDN Search on WScript.Shell

In case the documentation on MSDN is missing or just irrelevant (and that’s quite common) search “CreateObject <ProgID>” and the VBScript results are easily portable to C#.

Bing Search Results for: CreateObject WScript.Shell

 

But What about Macs?

Mac

COM+ Automation isn’t supported for Macs in Silverlight 4 Beta 1.
For a good reason, Mac doesn’t have COM+.

However, Microsoft have stated they are looking into getting COM+ support working in Silverlight 4 RTM on a Mac.

Macs have similar programmatic access to COM known as AppleScript.

image

It’s not that hard to read once you remove the added spaces and add some indents.
Once you get past the syntax, it’s easy to see those are the same classes and members on a Mac as are available for Windows.

The point here is that if Microsoft wants to enable Com Automation-like features on a Mac there are 2 options:

1) Enable executing AppleScripts.
This option will let us have the same amount of control on a mac machine as we do on a windows machine.

2) Add an overload to ComAutomationFactory.CreateObject() that calls the “Tell Application” command under the scenes and gets a AppleScript object.
This option would work extremely well for Office automation. For any other operating system feature, you’ll have to code OS access twice. 

 

If Microsoft chooses to not go ahead with Mac support in Silverlight 4 RTM, well, it’s not because they couldn’t.

 

What about Linux?

Moonlight Logo

Moonlight has had the superior security model since it first shipped.
Mono supports hosting Moonlight in a GtkWidget that has full-trust capabilities.
So while Silverlight has an elevated privileges model, Mono has a full trust model.

Read more about this model over at Miguel de Icaza’s blog @ Standalone Silverlight Applications.

 

Can I author my own COM+ Components?

image

It is possible yet unadvisable.
Authoring COM+ Components has a huge cost of learning COM+, and deploying and versioning COM+ Components.

COM+ was such a successful technology most of us left as soon as we could.

 

Can I author .Net components and then deploy them as COM+ components?

Woman Tangled in Wires - analogous to messing about with COM+

Jeremiah Morril has a blog post on how to go about doing that.

It is possible yet tremendously cumbersome.
Consider that you’ve just added a requirement for the full desktop CLR when running in Silverlight.
Some of your users might not have .Net (or the right .Net version) installed.
Additionally, you’ll still back into COM+ DLL versioning hell.

If you’re going to take that level of dependency it might be best if you moved over to WPF.

Or, if you insist on taking a .Net dependency you shouldn’t use COM+ for communication.
It’d be best to use WCF for that.
Michael Wolf has a good blog post demoing how to create that connection.

 

Fin

In this blog post we’ve reviewed many new features enabled by Silverlight 4 COM+ support.
We’ve also covered how to best use this new feature including tools, best practices and the possible future of this feature.

 

Leave A comment

I’d love to hear what you think of Silverlight 4 COM+ automation?
Better yet, if there’s some novel use of Silverlight 4 COM+ I haven’t shown here, feel free to link to it.

 

Sincerely,

-- Justin Angel



Comments

Rui Marinho Says:

what a great post Justin, i am looking at com+ sl project right now, trying to do a proof of concept. This was spetacular to know more features available.
do u know what u miss? some examples using events,the ComAutomationEvent class.

keep ap the good work.

regards
Rui

Michael Washington Says:

Very well done!

Jim Wightman Says:

This is probably the best blog post I've ever read - it's the complete works, the mini-bible of Silverlight COM+ interaction.

Thank you so much Justin for this. It's of great interest to a project I'm working on with getting Silverlight in WinPE working, in that of course in a deployment/recovery scenario, Filesystem and WMI access is a must. This blog post just saved me a lot of time in slowly learning the ropes wrt Silverlight and COM+. And it has advanced the realistic possibilities of using Silvelight, in WindowsPE, in a deployment solution.

Seriously great work.

Page Brooks Says:

Justin, you rock! Nice comprehensive post!

sacha Says:

Top post mate. Really great and highly useful

Fallon Says:

Loved it, but HTML would have been a lot better. I can't print the article.

Justin Angel Says:

Hi Fallon,

Thanks for the feedback.

Once Silverlight 4 ships with the new Printing Capabilities, I'll add a "Print" button to the blog.

jo Says:

Hi Justin,
Is their any ability for sending zpl commands to a printer in client network through socket programing in Silverlight 4.
I tried with Print Document but it is opening print API for each job.Is their any way to hide the API and print the job with the given printer IP.
Please advice me for solving this issue with some sample code.
Thanks
Joe

Davide Zordan Says:

Awesome!!

Mike Stokes Says:

Great post Justin, so well researched and conveyed :)

Unfortunately there is a big need for the COM stuff because there are some things Silverlight just can't do. For example we've implemented screen-shot functionality using Silverlight 4 and COM. The screen-capture class is in a simple COM object and it only requires .NET 2.0 framework - so is pretty much guaranteed to be available on XP, Vista, 7 etc. We tried Java applets firstly but the end-user experience is just pure-pain and not something we'd ever considder exposing our users to.

It's awesome that the SL team is looking at a similar capability of Mac as we were loathed to have to limit things to Windows only.

Awesome work :)

Jeff Handley Says:

Justin,

This is awesome stuff! Thanks for taking the time to illustrate all of this potential!

-Jeff

Anoop Madhusudanan Says:

Awesome Justin.. I asked you over twitter few days back, that how you implemented the T2S in your DNA app. You rock!!

Raghuraman Says:

Spot On !!!

Excellent coverage in a short and sweet Introduction !!!

Thank you for this write up.

Regards

Raghuraman

Fallon Says:

One more thing Justin & fellow posters.

For some reason, writing the text to c:\ works every time when I debug, but fails sometimes when I run it standalone.

It may be the virtual machine I'm on, so I'm wondering if anyone else had this problem?

Kjell-Åke Andersson Says:

Brilliant post! But it's not about COM+! It's about COM automation and that is a big difference!

Corrado Cavalli Says:

Awesome! didn't know MS has plans to support COM even on MAC platform...

Tushar Says:

This is great. I was waiting for this since a long time..
Things would be much better now...

Henrique Says:

Very good. Thank you for taking the time to write this awsome article.
Keep up the good work.

KierenH Says:

Justin, nice post. This is a cool technical feat, but it seems like a very unproductive way to write an app for windows... why not just write a .net app - share code between SL & .net.

I'd much prefer to use the the System.IO namespace then wire up the "Scripting.FileSystemObject".

The next step should be to wrap up the common COM objects so an app programmer never has to write the code in this post (i.e. use this under the covers to wire up System.IO namespace in SL). Otherwise, every large dev team that builds on this will end up writing the same wrapping code to hide the string driven interface.

Mikael Larsson Says:

Nice stuff, will add some WMI stuff to silverlightcom at codeplex.

Also if someone have problems with the FileSystemObject reading binary files, there is a workaround using ADODB.Stream, since SL have some limitations with Encoding. Justin i wonder did you find a way to enumerate files and folders using FileSystemObject? It works with dynamic in .NET 4 but not in SL4, not sure why.

If you want some wrapper classes check out http://silverlightcom.codeplex.com/

/Micke

Jose Luis Latorre Says:

Wow, great! that will help me much in a "small" project I will be dedicating this weekend ;).
Thanks for your insights and resources Justin!

Mark Monster` Says:

Awesome list. First complete Com+ list I've seen!

Good work Justin.

smitha Says:

I see a problem here.
When I run this app, the test.txt is getting created under ny user document path\appdata\local\test.txt on C:\. IT is not on the root of the drive. COuld you explain what could be happening here

Wolfgang Says:

Hey Justin,

I'm building a SL4-application which communicates with my business logic via IDispatch. This works fine in an out-of-browser scenario via installation (COM interop just like in your article).

Now, to make things a bit more interesting, I'm trying to host Silverlight in a small ATL application (via the Xcp hosting stuff from the tutorials). Now I want my elevation-heavy SL application to run in there, but I can find no way to give my native-hosted Silverlight plugin elevated trust.

Do you have an idea?

Cheers
Wolfgang

Laridhard Says:

Thank you very much for this explanation. It was something extraordinary.
For me, I'll make an application that communicates with a Pocket PC via USB and ActiveSync uses as communication protocol. The application must use dll system which can detect the presence of Pocket PCs and transfer data.

Do you have an idea?

LLT
BGI.

Prashant Danda Says:

Excellent article, very well explained (so that even a cave man can understand).

Writing files to anywhere we want using silverlight was something I was searching for days... great your post has answered it.

Anil Lakhani Says:

Can I open any image file of client side and get bytes[]?
instead of TextStream reading?

Michael Ramirez Says:

Can you use the COM+ support to listen on a port? Can I create an applicaiton that will listen on http://localhost:8080 for example?

Ganesh Majeti Says:

A very good feature exploitted by silverlight, i have somemor eto add,
now the class has been changed to AutomationFactory. and to add to the post this is only supported when u work on Out of browser SL application, where the Microsoft might be giving some more access than the in process browser sandbox.

Mehmet Says:

Will i able to save scanned image to memory as byte[] stream instead of saving image to file?
Thanks..

Patrik Löwendahl Says:

Great list of neat tricks for the platform dependent SL4 OOB app. Though, It's not COM+ you are automating, it is just plain COM. COM+ is the name of the component container / app server Microsoft evolved from MTS.

Amyo Says:

Wow, that’s great post. That’s absolutely helpful information.
Thank also that your site is built using Silverlight.

Mahesh Says:

That was great. Will the databse access feature available for silverlight 4 on windows mobile7 also? I mean with SQL Server Compact?

Sprici Says:

Hello , i downloaded project in .zip archive and then i opened it..... it says 30+errors :( .... : Error 2 The name 'ComAutomationFactory' does not exist in the current context D:\Users\smog\Desktop\SL4Com\SL4Com\MainPage.xaml.cs 58 37 SL4Com

ComAutomationFactory.CreateObject("Scripting.FileSystemObject")) -> 'ComAutomationFactory' this is error , how can i fix it ??

Sharker Khaleed Mahmud Says:

Way too good :)

Jordan Says:

Hey mate,

Have you tried this stuff outside of the VS debugger? Setting the registry keys to start the app with Windows works fine in the debugger but it gets an exception when you run it normally...

Beau Brownlee Says:

Great post. Thanks for sharing the knowledge, this is good stuff.

Anil Pandey Says:

Hi Very nice post,
Using the COM i have made a Screen capture OOb application, now i want to pass these capture images to my Silverlight web appliaction. For that i am using the polling duplex service, but that service is not running in OOB, is there any problem in OOB for using the Duplex service??
or by any other idea i can pass my deskop screen shot to my application...

kevin fan Says:

Great topic. It's very useful for my project. Thanks for sharing.

Bhagirath Patadia Says:

Very well presented... Thanks a lot

pippo Says:

very useful thanks
http://silverlightcom.codeplex.com

Jhelumi Says:

Great article.
I need to get the list of Printer Names installed on current machine and specific properties (tray, duplex etc) for the default or selected printer as well. How can I access that?
Any response would be much appreciated

Bernard Says:

After correction to AutomationFactory.CreateObject() and adding the missing
Using System.Runtime.InteropServices.Automation;

the sample I tried - writing a text file to c:\text.txt worked fine in the IDE and created the text file.

next I ran the HTML file generated and after Rt. Clicking on the form and Installing the Application, I tried clicking the button to no avail. No text.txt file was created anywhere and no error message or anything.

Do these samples actually work? Has anyone managed to get any of these samples to work outside of Debug in VS2010?

As Jordan says this appears to work only in VS2010 which makes it quite useless for actually doing anything useful, other than fooling around

Heath Says:

Based on your example above where you create an instance of a .NET class and work with it, I am trying to do the same thing with System.Diagnostics.PerformanceCounter so I can see how much memory the Silverlight app is consuming and force it to shutdown when it exceeds a certain point. Here's the code, but its not working, its telling me it can't find the progid. If anyone knows of a way of doing with with a shell command or something, I'd like to hear it as well.

public static string GetRamUsage(){

string result = "";
using (dynamic ramCounter = AutomationFactory.CreateObject("System.Diagnostics.PerformanceCounter")){

ramCounter.CategoryName = "Memory";
ramCounter.CounterName = "Available MBytes";

result = ramCounter.NextValue().ToString() + "MB";

ramCounter.Dispose();

}

return result;

}

Murugesan Says:

Hi,
Need help on Silverlight 4/.NET 4.0
I have a requirement to initiate RDP (with parameters passed) on client's desktop from my web application.
I tried creating windows forms control and embedding in ASP.NET web site. I am able to display windows forms button, on click on it, i am able to intiate RDP on client's brower, but i couldn't pass the parameters. Forum Techiees suggest to use Sliverlight to do this.
So, let me brief of my requirement.
1. User will access a web application on Desktop's browser
2. On click on a button, a RDP session should pop-up on user's desktop (Web application will pass IP address to RDP)
I am not used to silverlight and expecting some guidance.
Thanks in advance

Elroy Says:

Hi,

Nice informative article. May you suggest a complete reference for COM+ commands?

Thank you.

steve Says:

hi there,

wot a great post.
One thing i want to do is leverage all the linq code in my factories using EF4 within out of browser. Obviously I cannot directly call EF4 in SLOOB but it seems that i should be able to write a bridging COM+ object that commuicates with EF4 and call that from SLOOB. Does this sound correct?

Josh Says:

Awesome post.

How can you get the current usr identity?

Peter Voutov Says:

My question is why go through all this instead of building a WPF application where you have all the same goodies available to you through more elegant means (e.g. interop, win32, native .NET)?

Brian Says:

Excellent article! I noticed that you can used SendKeys, but is there any way one could use SetWindowsHookEx to get keyboard input without requiring focus? Or is there simply not a built-in COM+ class that provides access to it?
Thanks!

Abethan Says:

Hi Guys,

When I was running the code I am getting the below error
"No object was found registered for specified ProgID." in the line

using (dynamic CommonDialog = AutomationFactory.CreateObject("WIA.CommonDialog"))

Can anyone help me on this??

Thanks...

Wiles Says:

Have you tried this site?
http://forums.silverlight.net/forums/p/158851/356116.aspx

Looks like you need to confirm it is a method in the referenced dll.

Mike Says:

Brilliant post! But it's not about COM+! It's about COM automation and that is a big difference!

Until Silverlight 5, we don't have PInvoke and we can't minimize OOB apps to tray, but I wonder if it's possible to use COM api in full trust sl app to minimize the application in system tray? I'm not very fluent in COM/OLE, so anyone knows any com object which can minimize a window to tray?

If windows doesn't provide such COM object, is it possible to write one and make a setup.exe deployment package? What's your thought about?

Ben Says:

Dude, you rock. This opens up a whole new world of possibilities. WOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOts

Andre Says:

After 2 days of surfing trying to figure how to open a file open dialog and folder dialog (with full access) I found the code and wanted to share it.

public void FindFolder()
{
if (App.Current.IsRunningOutOfBrowser && App.Current.HasElevatedPermissions)
{

if (AutomationFactory.IsAvailable)
using (dynamic shell = AutomationFactory.CreateObject("Shell.Application"))
{


var y = shell.BrowseForFolder(0, "Choose a file or folder", 4096);

var a = y.Self;

string b = a.Path as string;

}
else
MessageBox.Show("This function is not Available");

}
else
MessageBox.Show("You may only use this option when this application is launched locally.");
}
// and

public static string FindExePath(string Filter)
{
try
{
if (App.Current.IsRunningOutOfBrowser && App.Current.HasElevatedPermissions)
{
if (AutomationFactory.IsAvailable)
using (dynamic shell = AutomationFactory.CreateObject("MSComDlg.CommonDialog"))
{


shell.DialogTitle = "Select File ";
shell.InitDir = @"c:\";
shell.Filter = Filter;
shell.maxfilesize = 8000;
var o = shell.ShowOpen;
var fullPath = shell.FileName;
if (fullPath != "")
return fullPath;
}
else
throw new Exception("This function is not Available");

}
else
throw new Exception("You may only use this option when this application is launched locally.");
}
catch (Exception ex)
{

throw ex;
}

return "";

}


Jose de Jesus Says:

I need to print from silverlight, if not possible, store images on the local computer


Thanks

lteamlao Says:

HRESULT: 0x800A0046
always appear as i load the and write a file any ways to create and exception

Simius Says:

Fantastic article Justin! Thanks for sharing your knowledge. I have a question: Is it possible to launch and app and send paramenters to it, from within Silverlight running in a browser? When I try to do it with the following command: using (dynamic shell = AutomationFactory.CreateObject("WScript.Shell")) { //Execute the command and wait for it to finish. ReturnVal = shell.Run(@"c:\windows\notepad.exe C:\Windows\win.ini", 1, false); } I get the following error: Silverlight does not have elevated permissions to run desktop applications. I looked at Silverlight's configuration settings, but there is nothing that I can set. Any help would be greatly appreciated. Thanks, Simius

Tracy Says:

if the COM object has an event how do you wire it code wise?

BTW, your blog as a silverlight is not allowing page search in FFox and IE?

rbrundritt Says:

I noticed there is a typo in the Location API code. You use

LatLongReportFactory.Latitude
LatLongReportFactory.Longitude

but there should be:

LatLongReport.Latitude
LatLongReport.Longitude

Bharath p Says:

Can i please know, how to access USB port and COM ports.
and I am also getting an exception "not registered projid" when i used dot net framework System.IO.Ports in Automatiofactory.Createobject()

Angel Says:

Hi! Is there any way to read a file as stream?

Amil Says:

Nice post and nice website design! I learned quite a bit about what is possible from SilverLight.