Vývoj vlastního CmdLetu v C# – příprava projektu Visual Studia

Vývoj vlastního PowerShell příkazu (CmdLet) je velice snadné a rychlé – ideální pro často opakované akce, případně akce u zákazníka, kde nemusí být vždy vzdálený přístup do jeho infrastruktury. Za posledních pár let už jsem jich napsal desítky a jsem jsem z toho stále nadšen, hlavně tou jednoduchostí, v podstatě jsem tím zcela nahradil jednoúčelové konzolové aplikace, kde již navíc není nutné řešit parsování argumentů, přehledná výpis na konzolovou obrazovku, všechno je již tak nějak vyřešeno. Jako příklad jednoho z CmdLetů bych uvedl snadné nasazení jedné naší aplikace hostované v Azure, kde s každým zákazníkem bylo vždy nutné přihlásit se na portál Azure, vytvořit hosting pro aplikaci, nastavit domény, standard mód, vytvořit a nastavit DB, connection string do aplikace, vytvořit storage provider, přihlašovací údaje zapsat do aplikace, nastavit zálohování, a tak bych mohl pokračovat….. až po finální nahrání zkompilované verze – prostě práce na minimálně 2-4h a to ještě s rizikem chyby. Nyní k tomu slouží dva CmdLety, jeden pro přihlášení k Azure, druhý už pro založení tenantu a provedení všech potřebných akcí zcela automaticky.

V tomto prvním díle ukážu pouze základ, založení Visual Studio projektu a jeho nastavení.

Základem vlastního PowerShell CmdLetu je založený projekt typu Class Library

image 

image

Volba verze .NET Frameworku je závislá na požadované verzi PowerShellu, pokud například skriptujete akce pro SharePoint 2010, musíte nastavit .NET Framework verze maximálně 3.5.

  • PowerShell 2.0 – .NET Framework 3.5
  • PowerShell 3.0 – .NET Framework 4
  • PowerShell 4.0 – .NET Framework 4.5
[more]

Po založení projektu je nutné ručně přidat potřebné reference, hlavní referencí je System.Management.Automation.dll, kterou naleznete (případně dle vaší verze PowerShellu):
C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0

image

Následně se již můžeme vrhnout na prvním CmdLet, pro první sample klasicky Hello World, založíme novou třídu a pojmenujeme jí GetHelloWorldMessage:

image

A začneme přidávat atributy a base třídu:

  • importujte namespace System.Management.Automation
  • třída GetHelloWorldMessage musí být zděděna z objektu PSCmdlet
  • přidejte atribut CmdLet k nově vytvořené třídě, první parametr definuje jednu z povolených akcí, respektive prefixů (na funkci samotného CmdLetu nemá vliv, jenom definuje standard) a druhý parametr určující název samotné Powershell funkce, tentokrát bez prefixu již daného výčtovým typem VerbsCommon, výsledek tedy může být:
    [Cmdlet(VerbsCommon.Get, „HelloWorldMessage“)]
    Samotný příkaz tedy bude dostupný jako Get-HelloWorldMessage

Samotnou funkčnost implementujete přetížením metody ProcessRecord(), která je definována v base PSCmdLetu a navíc přidáme jeden vstupní parametr pro specifikaci jména uživatele – tento parametr je označen jako povinný.

image

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace DevIT.PowerShell.Samples
{
    [Cmdlet(VerbsCommon.Get, "HelloWorldMessage")]
    public class GetHelloWorldMessage : PSCmdlet
    {
        [Parameter(Mandatory = true, HelpMessage = "Zadejte Vaše jméno.")]
        public string YourName { get; set; }
        protected override void ProcessRecord()
        {
            WriteVerbose("Spouštíme hrozně složitou akci...");
            WriteObject(string.Format("Vítejte, {0}!", YourName ?? string.Empty));
        }
    }
}

 

Nyní už jenom zkompilujeme, spustíme konzoli PowerShellu, nalistujeme adresář s výsledným DLL souborem a importujeme ho do sady dostupných PowerShell příkazů:

Import-Module .\DevIT.PowerShell.Samples.dll

Následně již můžeme zavolat příkaz Get-HelloWorldMessage:

image

Případně rovnou s parametrem Get-HelloWorldMessage -YourName “Pavel”

Nebo klasicky parametrem –? zobrazit dostupné volby, při prvním volání nápovědy však budete vyzván k přegenerování nápovědy, která se generuje na základě atributů uvedených u CmdLetu i samotných parametrů:

image

SharePoint 2013–nasazení farm solution pro SP2013 v compatibility módu 2010

SharePoint 2013 v kolekcích webů vytvořených nebo migrovaných v compatibility módu 2010, automaticky skrývá všechny features z WSP farm řešení pro verzi 2013. Pokud tedy provádíte upgrade, připojíte starou kolekci webů 2010, nainstalujete nové farm balíčky pro podporu SP2013 a chcete ponechat kolekci ve vzhledu 2010, alespoň do dokončení nutných úprav, feature z nových balíčků neuvidíte. Aby bylo možné aktivovat nové feature na starém UI, je potřeba provést retract balíčku ze všech webových aplikací a znovu provést deploy s atributem CompatibilityLevel:

[more]

Add-SPSolution „c:\DEV\DevIT.SharePoint.FBASuite\DEV2013-SQL\Builds\DevIT.SharePoint.FBASuite.wsp“
Install-SPSolution -Identity DevIT.SharePoint.FBASuite.wsp -WebApplication http://srvdev:555 -GACDeployment CompatibilityLevel {14,15}

 

Tím dojde k automatickému zkopírování všech potřebných souborů z WSP do obou klíčových složek SharePointu:

Pro verzi 2013:

c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\TEMPLATE\FEATURES\

Pro verzi 2010:

c:\Program Files\Common Files\microsoft shared\Web Server Extensions\14\TEMPLATE\FEATURES\

 

Nyní je feature viditelná i v kolekci webů UI verze 2010.

PowerGUI: Get-SPWeb : Microsoft SharePoint is not supported with version 4.0.30319.1 of the Microsoft .Net Runtime.

Po aktualizaci používá PowerGUI (http://www.powergui.org) standardně .NET Framework 4.0, ten ale není podporován pro práci s SharePointem 2010, podporován je pouze a jen 2.0 (3.5), jinak při práci s API vznikají výjimky:

Get-SPWeb : Microsoft SharePoint is not supported with version 4.0.30319.1 of the Microsoft .Net Runtime.

 

Přímo v nástroji PowerGUI nelze verzi .NET Frameworku změnit, je potřeba upravit konfigurační soubor aplikace:

  1. Otevřít cestu C:\Program Files (x86)\PowerGUI
  2. Najít a otevřít soubor ScriptEditor.exe.config v poznámkovém bloku
  3. Odmazat nebo zakomentovat řádek <supportedRuntime version=“v4.0″ sku=“.NETFramework,Version=v4.0″ />
  4. Po uložení a restartu aplikace se defaultně použije .NET Framework 2.0 (respektive s nástavbou na 3.5)

Zamknutí/odmknutí všech kolekcí webů ve webové aplikace najednou

Vstupní parametry:

  • webAppUrl – URL adresa webové aplikace
  • lockState
    • ReadOnly = zamčení pouze pro čtení
    • Unlock = odemčení zpět pro editaci

 

Příklad:

.\SetLockStateInAllSiteCollections.ps1 -webAppUrl http://srvdev:81/ -lockState ReadOnly

 

<#
   .Synopsis
	Sets/removes all site collection lock in web application
   .Example
   .\SetLockStateInAllSiteCollections.ps1 -webAppUrl http://srvdev:81/ -lockState ReadOnly
   .\SetLockStateInAllSiteCollections.ps1 -webAppUrl http://srvdev:81/ -lockState Unlock
   .Notes
	NAME: SetLockStateInAllSiteCollections.ps1
	AUTHOR: novotny@devit.cz
   .Link
	http://www.devit.cz
#>

param (
	[Parameter(Mandatory=$True)]
    [string]$webAppUrl,
	[Parameter(Mandatory=$True)]
    [string]$lockState
)

Add-PSSnapin Microsoft.SharePoint.PowerShell -erroraction SilentlyContinue

$webApp = Get-SPWebApplication $webAppUrl
$allSites = $webApp | Get-SPSite

foreach ($site in $allSites)
{
    Write-Host "Setting " -nonewline
    Write-Host $site.url -nonewline
    Write-Host " to $lockState..." -nonewline
    Set-SPSiteAdministration -LockState $lockState -Identity $site.url
    Write-Host "[OK]" -foregroundcolor green
}

SetLockStateInAllSiteCollections.ps1 (952,00 bytes)

Spuštění workflow na všech položkách/dokumentech pomocí PowerShellu

Vstupní parametry:

  • siteUrl – URL adresa na web kde se nachází seznam
  • listTitle – název seznamu
  • workflowName – název workflow (asociace)
  • workflowCulture – v jakém jazyce je název WF (default en-US)
  • restartIfRunning – pokud WF běží, zastaví se (default je NE, vynechá se zpracování)

 

Příklad:

.\StartListWorkflowOnAllItems.ps1 -siteUrl http://srvdev:81/ -listTitle „Documents“ -workflowName „Approval WF“ -restartIfRunning $true

 

StartListWorkflowOnAllItems.ps1 (2,62 kb)

 

<# 
   .Synopsis 
    Start workflow on all items in list or document library
   .Example
   .\StartListWorkflowOnAllItems.ps1 -siteUrl http://srvdev:81/ -listTitle "Documents" -workflowName "Approval WF" -restartIfRunning $true
   .Notes 
    NAME: StartListWorkflowOnAllItems.ps1
    AUTHOR: novotny@devit.cz
   .Link 
    http://www.devit.cz
#> 

param (
    [Parameter(Mandatory=$True)]
    [string]$siteUrl,
    [Parameter(Mandatory=$True)]
    [string]$listTitle,
    [Parameter(Mandatory=$True)]
    [string]$workflowName,
    [string]$workflowNameCulture = "en-US",
    [boolean]$restartIfRunning = $true
)

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") 

$web = Get-SPWeb -Identity $siteUrl

Write-Host "Opening site '$($web.Title)'"

$manager = $web.Site.WorkFlowManager

$list = $web.Lists[$listTitle]
if ($list -eq $null) {
    Write-Host "Unable to find list '$listTitle'" -ForegroundColor Red
    return
}
 
$assoc = $list.WorkflowAssociations.GetAssociationByName($workflowName, $workflowNameCulture)
if ($assoc -eq $null) {
    Write-Host "Unable to find WF association '$workflowName' for culture '$workflowNameCulture'" -ForegroundColor Red
    return
}
 
$data = $assoc.AssociationData
$items = $list.Items

Write-Host "Starting workflows in list '$($list.Title)'"
foreach($item in $items)
{
    $itemName = $item.Title
    if ($item.FileSystemObjectType -eq [Microsoft.SharePoint.SPFileSystemObjectType]::File) {
        $itemName = $item.File.Name
    }

    Write-Host "Starting on ID:$($item.ID) - '$itemName'"
    try {
        $runningWf = $null

        foreach($wf in $item.Workflows) {
            if ($wf.AssociationId -eq $assoc.Id -and ($workflow.InternalState -ne 'Completed' -and $workflow.InternalState -ne 'Cancelled')) {
                $runningWf = $wf
                break
            }
        }

        if ($restartIfRunning -eq $false -and $runningWf -ne $null) {
            Write-Host "   Already Running... Skipping" -ForegroundColor Green
            continue
        }

        if ($runningWf -ne $null) {
            Write-Host "   Stopping existing WF" -ForegroundColor Green
            [Microsoft.SharePoint.Workflow.SPWorkflowManager]::CancelWorkflow($runningWf)
        }
        
        $wf = $manager.StartWorkFlow($item, $assoc, $data, $true)
        Write-Host "   OK" -ForegroundColor Green
    }
    catch [System.Exception] {
        Write-Host "   ERROR:" $_ -ForegroundColor Red
    }
}
  
$manager.Dispose()
$web.Dispose()