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!
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):
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:
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
