COM support in Monad

I was reading Tony’s Blog MSHFORFUN about using Microsoft Text to Speech engine to output using voice. He created an Interop assembly for Microsoft TTS com object and used the resulting .Net assembly to do voice output. Monad provides great support for .Net classes and in many cases, generating Interop assembly is the only way to access unmanaged components. However, there are cases where this is not needed. The above example is one of them.

Monad provides great support for creating and using COM objects. You can create a COM object using the same new-object cmdlet that you use for .Net objects. You do this by specifying –ComObject parameter with the ProgID of the COM object you want to create.

For Example:

    Let us create Internet explorer application and have it navigate to a particular website.

$ie = new-object -comobject Internetexplorer.application

$ie.Navigate2("https://www.microsoft.com")

$ie.visible=1

    This creates a new Internet Explorer application COM object. Invokes a method and sets a property on the COM object.

Determining what methods and properties are supported in a COM object is a snap using get-member cmdlet.

MSH C:\Scripts\> $fso = new-object –com scripting.filesystemobject

MSH C:\Scripts\> $fso | get-member

   TypeName: System.__ComObject#{2a0b9d10-4b87-11d3-a97a-00104b365c9f}

Name Member Type Definition

---- ---------- ----------

BuildPath Method string BuildPath (string, string)

CopyFile Method void CopyFile (string, string, bool)

CopyFolder Method void CopyFolder (string, string, bool)

CreateFolder Method IFolder CreateFolder (string)

CreateTextFile Method ITextStream CreateTextFile (string, bool, bool)

DeleteFile Method void DeleteFile (string, bool)

DeleteFolder Method void DeleteFolder (string, bool)

DriveExists Method bool DriveExists (string)

FileExists Method bool FileExists (string)

FolderExists Method bool FolderExists (string)

GetAbsolutePathName Method string GetAbsolutePathName (string)

GetBaseName Method string GetBaseName (string)

GetDrive Method IDrive GetDrive (string)

GetDriveName Method string GetDriveName (string)

GetExtensionName Method string GetExtensionName (string)

GetFile Method IFile GetFile (string)

GetFileName Method string GetFileName (string)

GetFileVersion Method string GetFileVersion (string)

GetFolder Method IFolder GetFolder (string)

GetParentFolderName Method string GetParentFolderName (string)

GetSpecialFolder Method IFolder GetSpecialFolder (SpecialFolderConst)

GetStandardStream Method ITextStream GetStandardStream (StandardStreamTypes, bool)

GetTempName Method string GetTempName ()

MoveFile Method void MoveFile (string, string)

MoveFolder Method void MoveFolder (string, string)

OpenTextFile Method ITextStream OpenTextFile (string, IOMode, bool, Tristate)

Drives Property IDriveCollection Drives () {get}

Now, coming back to using TTS engine, we could rewrite Tony’s script as follows:

begin

{

    $SpVoice = new-object -com SAPI.SpVoice

    $count = 0

   

}

process

{

    if ($_)

    {

       $count++

       $StringToSay = $_ | out-string

       "$count: $StringToSay"

  

        $SpVoice.Speak($StringToSay)

    }

    else

    {

       "null object"

    }

}

end

{

  "Number of objects successfully output: $count"

}

I have removed the code that saves the voice output to a file, but it is easy to add that functionality to the above code. This code creates the Speech API’s SpVoice COM object directly, for each object coming through the pipeline, converts the object to string, and uses the Speak method on COM object to do Voice output.

Try it out!

There is one caveat with using COM objects in Monad though. In VBScript or JavaScript, You can release a COM object by setting the variable to null. Scripting engine underneath calls the Release method on the COM object to decrement the reference count and when the reference count reaches zero, COM object is released. In managed world, releasing COM object is not deterministic. COM object is released when the variable holding the COM object is garbage collected. This might be ok in many cases, but there are cases where we need to release the COM object immediately. .Net framework provides a way to solve this problem. You can use

System.Runtime.InteropServices.Marshal.ReleaseComObject()

to release the COM object. However, I do not recommend this. .Net framework Interop layer maintains only one reference count on the underlying COM object regardless of how many managed clients refer to that object. When you call ReleaseComObject, underlying COM object is released regardless of the number of clients. These other clients of COM object will get InvalidComObjectException when they try to invoke a method or access a property. Be careful when you use this approach and be sure that you are releasing the last reference to the COM object.

Monad is theonly scripting platform that provides access to .Net, WMI, COM in a well-integrated manner. Enjoy the power but use it with caution. J