Programovanie CmdLet-ov pre PowerShell v C#

Publikoval Michal Kočí dňa 28.06.2006 o 23:41 v kategórii PowerShell

CmdLet alebo CommandLet sa dá chápať ako príkaz v PowerShell-i (ďalej len PS). V samotnej podstate sa však jedná o triedu naprogramovanú v niektorom z .Net jazykov. Preto je vcelku jednoduché si v prípade potreby a/alebo chuti nejaký CmdLet naprogramovať.

Keďže sa jedná o triedu napísanú v niektorom .Net jazyku, je vhodné mať Visual Studio, prípadne jeho odľahčenú verziu z rady Express, napríklad Microsoft Visual C# 2005 Express Edition, ktorá sa dá bezplatne stiahnuť aj bezplatne používať dokonca aj na komerčné účely. Je potrebné triedu prísať pre .Net Framework (ďalej len FW) 2.0. Projekt ktorý bude obsahovať CmdLet musí referencovať assembly System.Management.Automation.Dll, ktorá sa nainštaluje spolu s PS, pretože CmdLet je trieda dediaca z triedy CmdLet alebo PsCmdLet nachádzajúcej sa práve v spomenutej assembly.

Tak ak to zhrniem, mali by ste mať na počítači nainštalované:

Power Shell je momentálne ešte len vo verzii Release Candidat 1, z čoho vyplýva niekoľko dôležitých faktov:

  • Nie je zaručené, že sa niektoré kľudne aj klúčové vlastnosti do ostrej verzie nezmenia
  • Nápoveda je taká aká je, čiže poskromná

I keď je možné kód písať v textovom editore (napríklad Poznámkový blok) a prekompilovať cez príkazovú riadku kompilátorom, ktorý je súčasťou .Net FW, jednoduchšie je mať naištalované aj vývojové prostredie, napríklad Visual C# 2005 Express:

Či už použijete vývojové prostredie, alebo budete assembly s CmdLet-om kompilovať z príkazovej riadky, nezabudnite referencovať assembly System.Management.Automation.Dll, ktorú nájdete v adresári kde sa Vám nainštaloval Power Shell (pri čtandardnej inštalácii je to adresár c:\Program Files\Windows PowerShell\v1.0).

CmdLet je teda trieda zdedená z triedy Cmdlet alebo PsCmdlet. Navyše, trieda musí byť označená atribútom CmdletAttribute, kde špecifikuje sloveso a podstatné meno, ktoré sa bude používať pri volaní CmdLet-u z PS. Ak s PS pracujete, iste ste si všimli, že názov každého CmdLet-u sa skladá zo slovesa (anglicky verb), pomlčky a podstatného mena (anglicky noun), pričom podstatné meno sa uvádza v jednotnom čísle (napríklad Get-Process, Set-Date, ...). Pri tvorbe CmdLet-u je vhodné tento štandard dodržiavať a rovnako vhodné je použitie štandardného slovesa. Štandardné slovesá sa dajú získať ako statické polia niekoľkých statických tried, napríklad sloveso Get ako člen Get triedy VerbsCommon alebo sloveso Start ako člen Start triedy VerbsLifeCycle.

[Cmdlet(VerbsCommon.Get, "MsSqlDatabase")]
public class GetMsSqlDatabaseCommand: Cmdlet
{
}

V príklade je vhodné si všimnúť:

  • trieda musí byť verejná (public class)
  • trieda je označená atribútom CmdletAttribute (slovo Attribute je nepovinné)
  • CmdLet bude volateľný ako Get-MsSqlDatabase

Trieda dediaca z triedy Cmdlet (prípadne z triedy PsCmdlet, ktorá tiež dedí z Cmdlet) vykonáva samotné spracovanie v jednej z prepísaných metód BeginProcessing, ProcessRecord a/alebo EndProcessing. V ktorej, o tom je potrebné sa rozhodnúť na základe toho, čo má CmdLet vykonávať, aké parametre bude príjímať a či parametre bude prijímať z príkazovej riadky alebo z pipeline. Je vhodné poznať spôsob, ako a kedy sú tieto metódy zavolané PS-om:

  • Keď je zavolaný CmdLet a sú k dispozícii všetky povinné parametre, PS zavolá metódu BeginProcessing. V tejto je možné pripraviť triedu na samotné spracovanie prípadne vykonať samotné spracovanie ak CmdLet prijíma parametre z príkazového riadku a nie z pipeline. Táto metóda je zavolaná presne raz.
  • Metóda ProcessRecord je zavolaná raz alebo viac krát, v závislosti od toho, koľko "záznamov" dostane CmdLet na spracovanie. Zjednodušene povedané, ak sú parametre pre CmdLet predané cez príkazový riadok, je táto metóda zavolaná raz. Ak CmdLet prijíma parametre z pipeline, potom je táto metóda zavolaná toľko krát, koľko záznamov (objektov) CmdLet cez pipeline prijme.
  • Na konci spracovania je zavolaná metóda EndProcessing, rovnako je zavolaná presne raz a aj ona je vhodná na spracovanie v prípade príjmu parametrov z príkazového riadku.

Ak sa na to pozrieme z druhej strany a opäť zjednodušene, tak platí, že:

  • V prípade že CmdLet prijíma parametre iba z príkazového riadku, je dobré použiť metódu BeginProcessing, respektíve EndProcessing.
  • V prípade že CmdLet prijíma parametre z pipeline, treba spracovanie vykonávať v metóde ProcessRecord a prípadné akcie ktoré treba vykonať pred spracovaním jednotlivých záznamom treba robiť v metóde BeginProcessing a akcie ktoré treba vykonať po spracovaná všetkých záznamom treba robiť v metóde EndProcessing.

[Cmdlet(VerbsCommon.Get, "MsSqlDatabase")]
public class GetMsSqlDatabaseCommand: Cmdlet
{
  protected override void BeginProcessing()
  {
  }

  protected override void ProcessRecord()
  {
  }

  protected override void EndProcessing()
  {
  }
}

Ako prvý príklad CmdLetu vytvoríme jednoduchý CmdLet Get-MsSqlDatabase, ktorý sa pripojí na Microsoft SQL server a zistí všetky jeho databázy. Pripojenie sa pre jednoduchosť bude konať v kontexte prihláseného užívateľa, teda bude použité integrované Windows overenie.

Aby bolo možné sa pripojiť k MS SQL Serveru, je potrebné špecifikovať ku ktorému serveru sa má CmdLet pripojiť. Názov serveru bude CmdLet prijímať z príkazového riadku. Aby CmdLet mohol prijímať parametre z príkazoveho riadku (ale vlastne aj z pipeline), je potrebné aby mal verejnú vlastnosť (public property), ktorá bude navyše označená atribútom ParameterAttribute. Týmto sa špecifikuje najmä:

  • Či je parameter povinný - Mandatory
  • Poradie parametra v príkazovom riadku, ak sa nepoužije názov parametra - Position
  • Či je hodnota paramatra načítávaná z pipeline

Pridáme teda do triedy vlastnosť ServerName, bude to prvý parameter príkazového riadka, nebude možné ho plniť cez pipeline a bude to povinný parameter:

[Parameter(Mandatory=true, Position=0, ValueFromPipeline=false)]
public string ServerName
{
  get { return serverName; }
  set { serverName = value; }
}

Čo je dobré vedieť:

  • Tým, že parameter je povinný, ak nebude zadaný priamo, PS si jeho hodnotu "vypýta"
  • Tým, že parameter má pozíciu 0, nie je potrebné špecifikovať jeho názov, stačí uviesť jeho hodnotu.
  • Ak chceme špecifikovať aj názov parametra, nie je potrebné použiť jeho celý názov. Stači použiť jeho časť, pretože jeho ľubovoľná časť (nie zo stredu názvu, ale od začiatku názvu) je jedinečná. Možno sa teda na parameter odkazovať názvom ServerName, ale aj Server, či len S.

Ak chceme, môžeme parametru priradiť aj alternatívny názov - alias. Slúži na to atribút AliasAttribute. Mohli by sme parametru ServerName priradiť aliasy Srv a

[Parameter(Mandatory=true, Position=0, ValueFromPipeline=false)]
[Alias("Srv", "DbServer")]
public string ServerName
{
  get { return serverName; }
  set { serverName = value; }
}

V tomto momente máme triedu, ktorá ešte nevykonáva žiadnu činnosť, ale už vie prijať parameter. V tomto momente by bolo možné CmdLet volať ľubovolným z nasledujúcich spôsobov (ak by bol zaregistrovaný v PS). Vychádzame z predpokladu, že nás zaujímajú databázy na serveri dbsrv1:

  • Get-MsSqlDatabase dbsrv1
  • Get-MsSqlDatabase -ServerName dbsrv1
  • Get-MsSqlDatabase -Server dbsrv1
  • Get-MsSqlDatabase -S dbsrv1
  • Get-MsSqlDatabase -Srv dbsrv1
  • Get-MsSqlDatabase -DbServer dbsrv1

Výsledok všetkých vyššie uvedených volaní by bol rovnaký.

Keďže tento CmdLet prijíma iba jeden parameter a tento prijíma z príkazoveho riadku a nie z pipeline, spracovanie vykonáme v metóde EndProcessing. Skúsime sa pripojiť na požadovaný databázový server a zistíme názvy databáz z tabuľky sysdatabases z databázy master. Na zápis názvov databáz je použitá metóda WriteObject, ktorá ako prvý parameter prijáma samotný objekt, ktorý má byť výstupom a druhý parameter určuje, či výstupný objekt je členom kolekcie. V našom prípade je, pretože výstupom nášho CmdLet-u je kolekcia názvov databáz databázového servera.

protected override void EndProcessing()
{
  string cs = string.Format(
    "Data Source={0};" +
    "Initial Catalog=master;" +
    "Integrated Security=SSPI;", 
    serverName);

  string sc = "select dbid, name, crdate, filename " +
    "from master..sysdatabases order by name";

  SqlConnection c = new SqlConnection(cs);
  SqlCommand cmd = new SqlCommand(sc, c);

  try
  {
    c.Open();
    SqlDataReader dr = cmd.ExecuteReader();

    if(dr.HasRows)
    {
      while(dr.Read())
      {
        WriteObject((string) dr["name"], true);
      }
    }

    dr.Dispose();
  }
  finally
  {
    cmd.Dispose();
    c.Close();
    c.Dispose();
  }
}

Keď máme triedu hotovú, isto by sme si radi CmdLet vyskúšali. Na to aby sme mohli CmdLet použiť v PS je potrebné vytvoriť SnapIn. SnapIn je akýsi zásuvný modul obsahujúci CmdLet-y a Provider-y. Ak chcete zaregistrovať iba ich istú podmnožinu, je potrebné SnapIn zdediť z triedy CustomPSSnapIn. V našom prípade budeme chcieť zaregistrovať všetky CmdLety obsiahnuté v assembly, preto SnapIn zdedíme z triedy PSSnapIn. V tomto momente máme síce len jeden CmdLet, ale na demonštráciu tvorby CmdLet-ov budeme programovať ešte jeden.

Popis uvedených tried nájdete tu:

Tým, že trieda zdedená z PSSnapIn zaregistruje všetky CmdLet-y stačí, ak v nej prepíšeme tri vlastnosti:

  • Name (názov SnapIn-u)
  • Vendor (poskytovateľ SnapIn-u)
  • Description (popis SnapIn-u)

Kód potom vyzerá nasledovne:

[RunInstaller(true)]
public class MsSqlPsSnapIn : PSSnapIn
{
  public MsSqlPsSnapIn() : base()
  {
  }

  public override string Name
  {
    get { return "MsSqlPsSnapIn"; }
  }

  public override string Vendor
  {
    get { return "Mifko"; }
  }

  public override string Description
  {
    get {return "This is a snapin that includes the *-MsSql* cmdlets"; }
  }
}

Všimnite si, že trieda MsSqlPsSnapIn je označená atribútom RunInstaller. Tento zabezpečí inštalovateľnost assembly nástrojom Installer Tool (InstallUtil.Exe). Aby šiel projekt skompilovať, je ešte potrebné pridať referenciu na assembly System.Configuration.Install.Dll, v ktorej sa nachádza trieda Installer, z ktorej PsSnapIn dedí.

Náš projekt sa teda skladá z dvoch hlavných súborov

  • MsSqlPsSnapIn.cs obsahuje SnapIn v podobe triedy MsSqlPsSnapIn
  • GetMsSqlDatabaseCommand.cs obsahuje CmdLet v podobe triedy GetMsSqlDatabaseCommand

Uvedené súbory si môžete stiahnuť ako projekt pre Visual Studio, respektí Visual C#. V súbore ponechávam aj prekompilovanú assembly pre tých, ktorým sa nechce projekt kompilovať, prípadne nemajú možnosť kompilácie: MsSqlSnapIn.v1.0.zip (8KB).

Po prekompilovaní projektu treba výslednú assembly nainštalovať spomenutým nástrojom nasledovne:

"c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe" MsSqlPsSnapIn.dll

V prípade že by ste chceli assembly odinštalovať, poslúži Vám nasledovný príkaz:

"c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe" /u MsSqlPsSnapIn.dll

Tak, keď je assembly nainštalovaná, je potrebné ju v PS pridať. Ak sa chcete presvedčiť, či je assembly v systéme nainštalovaná a je možné ju v PS pridať, použite príkaz:

Get-PSSnapin -Registered

Jeho výstupom by malo byť niečo podobné tomuto:

Name : MsSqlPsSnapIn PS
Version : 1.0
Description : This is a snapin that includes the *-MsSql* cmdlets

To svedčí o tom, že assembly je nainštalovaná a že je možné SnapIn pridať do PS konzoly a následne využívať jeho CmdLet-y. Pridanie SnapIn-u do konzoly sa vykonáva príkazom Add-PSSnapin:

Add-PSSnapin MsSqlPsSnapIn

Hotovo. Akonáhle je SnapIn pridaný, je možné použiváť CmdLet Get-MsSqlDatabase. V krátkosti teda zhrniem hlavné body, ktoré treba vykonať, aby ste naprogramovali CmdLet a mohli ho používať v PS:

  • Naprogramovanie jedného alebo viacerých CmdLet-ov
  • Naprogramovanie SnapIn-u
  • Pridať referencie na System.Configuration.Install.Dll a System.Management.Automation.Dll
  • Skompilovanie do assembly
  • Inštalácia assembly
  • Pridanie SnapIn-u do PS

Toľko ukážka tvorby jednoduchého CmdLetu. Nabudúce sa pozrieme na trochu pokročilejšie možnosti programovania CmdLet-ov, konkrétne na:

  • Výstup objektov do pipeline
  • Príjem parametrov z pipeline
  • Akceptovanie rôznych sád parametrov CmdLet-om
  • Formátovanie výstupu

Mohlo by ťa tiež zaujímať

Páčil sa ti príspevok?

Zdieľaj príspevok alebo si ho odlož na neskôr

Sleduj ma

Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.

Komentáre

K tomuto článku nie su pridané žiadne komentáre.

Pridať komentár

Máš niečo zaujímavé povedať k článku? Pridaj to k článku ako komentár. Spam, reklamu alebo inak nerelevantné komentáre okamžite mažem.

Pridanie komentára sa nepodarilo. Oprav si prosím chyby.