In Part 1 of this article, we looked at using the PowerShell strengths to automate the process of updating several databases through the PivotRunner tool.
Now, we want to go further and create a PowerShell command, better known as a Cmdlet.
Build the Cmdlet
A Cmdlet can be built directly in a Powershell script, or through the .NET Framework. We need to inherit from System.Management.Automation.Cmdlet and define its naming attributes therefor.
By agreement, the name of a Cmdlet consists of a verb, followed by a dash and a name (e.g: Get-ChildItem andAdd-PSSnapIn):
using System.Management.Automation; namespace CodeFluentEntitiesCmdlet { [Cmdlet(VerbsData.Update, "CFEDatabase", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] public class UpdateCFEDatabase : Cmdlet { } }
Here, the Cmdlet’s name will be Update-CFEDatabase.
Use the following PowerShell command: Copy ([PSObject].Assembly.Location) C:\MyDllPath to find the System.Management.Automation library
SupportsShouldProcess and ConfirmImpact attributes allow the Cmdlet to use the PowerShell Requesting Confirmation feature.
The Cmdlet abstract class includes a fairly advanced command parameters engine to define and manage parameters:
[Parameter(Mandatory = true)] public string ConnectionString { get; set; } [Parameter(Mandatory = true)] public string PivotFilePath { get; set; }
The Mandatory term is used to warn the command parameters engine of whether or not a parameter is required.
Cmdlet also exposes some methods which can be overriden. These pipeline methods allow the cmdlet to perform pre-processing operations, input processing operations, and post-processing operations.
Here, we’ll just override the ProcessRecord method:
protected override void ProcessRecord() { // Process logic code }
Then, we need to use the PivotRunner which is located in the CodeFluent.Runtime.Database assembly.
The tool takes the connection string and the pivot script producer output file as parameters:
using CodeFluent.Runtime; using CodeFluent.Runtime.Database.Management.SqlServer; private void UpdateDatabase() { try { PivotRunner runner = new PivotRunner(PivotFilePath); runner.ConnectionString = ConnectionString; if (!runner.Database.Exists) { WriteObject("Error: The ConnectionString parameter does not lead to an existing database!"); return; } runner.Run(); } catch (Exception e) { WriteObject("An exception has been thrown during the update process: " + e.Message); } }
Do not forget to reference CodeFluent.Runtime.dll and CodeFluent.Runtime.Database.dll!
Moreover, we can recover the PivotRunner output (internal logs) by providing an IServiceHost implementation:
public class CmdletLogger : IServiceHost { private Cmdlet _cmdLet; public CmdletLogger(Cmdlet cmdlet) { _cmdLet = cmdlet; } public void Log(object value) { _cmdLet.WriteObject(value); } } runner.Logger = new CmdletLogger(this); runner.Run();
Powershell integration
The Cmdlet is now finished! Image may be NSFW.
Clik here to view.
Now we’ll see how to call it from Powershell! Here, we have several options, but we shall see the PSSnapIn one.
The “Writing a Windows PowerShell Snap-in” article shows that a PSSnapIn is mostly a descriptive object which inherits from System.Configuration.Install.Installer and is used to register all the cmdlets and providers in an assembly.
So, let’s implement our Powershell snap-in:
using System.ComponentModel; using System.Management.Automation; namespace CodeFluentEntitiesCmdlet { [RunInstaller(true)] public class CodeFluentEntitiesCmdletSnapin01 : PSSnapIn { public CodeFluentEntitiesCmdletSnapin01() : base() { } public override string Name { get { return ((object)this).GetType().Name; } } public override string Vendor { get { return "SoftFluent"; } } public override string VendorResource { get { return string.Format("{0},{1}", Name, Vendor); } } public override string Description { get { return "This is a PowerShell snap-in that includes the Update-CFEDatabase cmdlet."; } } public override string DescriptionResource { get { return string.Format("{0},{1}", Name, Description); } } } }
Then, we build our solution which contains our Cmdlet and the PSSnapIn and finally register the built library thinks to the InstallUtil.exe (located in the installation folder of the .NET Framework):
Clik here to view.

Administrator rights are required.
By using the “Get-PSSnapIn –Registered” Powershell command, we can observe that our PSSnapIn is well registered. This component can now be used into your Powershell environment:
Image may be NSFW.
Clik here to view.
The “Add-PSSnapIn” command enables us to use our Cmdlet into the current session of Powershell.
As result, we can update our previously built Powershell script:
param([string[]]$Hosts, [string]$PivotFilePath, [switch]$Confirm = $true) Add-PSSnapin CodeFluentEntitiesCmdletSnapin01 if ($Hosts -eq $null -or [string]::IsNullOrWhiteSpace($PivotFilePath)) { Write-Error "Syntax: .\UpdateDatabase.ps1 -Hosts Host1[, Host2, ...] -PivotFilePath PivotFilePath" break } [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null Write-Host "-========- Script started -========-" $Hosts | foreach { $srv = new-object ('Microsoft.SqlServer.Management.Smo.Server') $_ $online_databases = $srv.Databases | where { $_.Status -eq 1 -and $_.Name.StartsWith("PivotTest_") } if ($online_databases.Count -eq 0) { Write-Error "No database found" break } Write-Host "Database list:" $online_databases | foreach { Write-Host $_.Name } [string]$baseConnectionString = "$($srv.ConnectionContext.ConnectionString);database=" $online_databases | foreach { Update-CFDatabase -ConnectionString "$($baseConnectionString)$($_.Name)" -PivotFilePath $PivotFilePath -Confirm:$Confirm } } Write-Host "-========- Script ended -========-"
We can now simply deploy all changes we’ve recently made on our databases thanks to the Cmdlet and PivotRunner components.
The source code is available for download.
Happy PowerShelling !
The R&D team
Image may be NSFW.
Clik here to view.
Clik here to view.
