Any (Windows user) CLI enthusiast should already be aware about PowerShell, MS latest bet in this area.

I must confess that even being aware about it I haven't had the time to use it other than for really simple tests. You know,

most of the time we need to get something done instead of learning a new set of shell commands, syntax, philosophy, etc.

But this changed recently due to some cool guys asking about how they could use Db4o to store PowerShell objects.Now

I have the motivation (and some allocated time) to pursue this goal, cool!

I do suggest that you give PowerShell a serious try (this way you'll gain some familiarity with it) before you continue to read this post.

Among other places, you can find a lot of information regarding PowerShell here, here and here (for programing purposes). I am not a expert in the subject, so I am going to explain only the basics to get the code in this post to work.

Have you gotten yourself comfortable with PowerShell already? Ok, grab the source code for this post and have fun :)  (svn co https://source.db4o.com/db4o/trunk/sandbox/Adriano/Blogs/PowerShell/)

My first step was to download PowerShell 2 (CTP 3) and install it. Then I just took a sample from the powershell community, compiled

and installed the new cmdlet.

Well, "just" is an euphemism of course. It took me some time to understand what was required, so, in order to make it simple to you, I'll give a short explanation about what's required:

  • In order to compile the code you need to set references to PowerShell assemblies located under "%ProgramFiles%\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0"  (or, in PowerShell syntax: "$env:ProgramFiles\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0")

  • Register your cmdlet (i.e, make PowerShell aware of our new cmdlet). In a "PowerShell" type (this requires administrator privileges):

    PS C:\> $env:windir\Microsoft.Net\Framework\V2.0.50727\InstallUtil.exe PATH-TO-YOUR-CMDLET-DLL

  • Finally, add your cmdlet to the current shell (take a look in export-console cmdlet if you don't want to repeat this everytime you open a shell):

    PS C:\> Add-PSSnapin Db4o.PowerShellSnapin

Now we are ready to run our cmdlet.

PS C:\Users\adriano> select-db4o-object

Once I get past this step I just tried to store / query a simple POCO (defined in the same project) using Db4o and it just worked ®.

Since it worked flawlessly I started to dig into how to define objects dynamically inside PowerShell as this could pose problems for Db4o. After some research

I found the add-member cmdlet, which is used to dynamically add members to objects. So, the next thing I've tried was:

PS C:\> $obj = ""
PS C:\> $obj = $obj | add-member -membertype noteproperty Name Adriano -passthru
PS C:\> $obj = $obj | add-member -membertype noteproperty Country Brazil -passthru
PS C:\> $obj = $obj | add-member -membertype scriptmethod Print {$this.Name + ": " + $this.Country} -passthru

This creates a new object with properties Name and Country and a Print() method .

Then I've tried to persist this object into Db4o and got an exception back saying that some collection object could not be stored in Db4o because it had no usable public constructors (a constructor that doesn't throw when called with default values (null for references, 0 for int, etc.). As explained here, some classes (for instance collections) require their constructor to be called in order to get a valid object

so this was a show stopper for me. Again, after some investigation I realized that I could write a custom typehandler and persist only PSObject's "interesting" bits. At least for the basic 

scenario it worked fine:

PS C:\> add-db4o-object -DatabasePath test.odb -Item $obj
PS C:\> $t = select-db4o-object -DatabasePath test.odb
PS C:\> $t.Print()
Adriano: Brazil

(note that the database file [in this sample, test.odb] will always be created / looked up in the user home folder, i.e, $env:homepath) (of course you can change this behavior passing a full path to Db4o)

Basically this typehandler just acts as a translator, creating (and storing) an instance of PSObjectWrapper (a more Db4o friendly class) for each PSObject. When reading it just goes trough the inverse path: it reads a PSObjectWrapper from the database, instantiate a new PSObject and set its properties based on the just read object; finally it returns this new object as if it was read from the database.

What is still missing in this cmdlet is a way to query for objects in the database and to handle multiple references to an object correctly but I'll leave this as an exercise for the reader ;)

Another possible approach  would be to emit a "strong" type for every PSObject in a object graph; this would make querying simpler and faster but would not fix the issue with multiple references for objects.

Let me know if you have ideas, suggestions or questions.

Best

Adriano