powershell-to-rql.png

Gewusst wie! "Powershell to RQL"

Bei der täglichen Arbeit mit dem OpenText™ Web Site Management (RedDot) Server, steht man immer wieder neuen Anforderungen gegenüber. Diese liegen im Bereich von:

  • Content-Import
  • Steuerung des CMS von Drittsystemen
  • Automatisierung von regelmäßigen Workflows
  • usw.

Dazu habe ich mir vor längerer Zeit, die Microsoft PowerShell angesehen und mich direkt in die Funktionsweise dieser Shell verliebt. Im Zusammenspiel mit den Management Server APIs, wird die PowerShell zu einem extrem mächtigen Hilfsmittel. Vor allem die PowerShell ISE ist eine sehr hilfreiche Plattform um sich in das Thema einzuarbeiten.
 

Was ist PowerShell?

PowerShell ist eine Automatisierungsplattform und eine Skriptsprache für Windows und Windows Server, mit der Sie die Verwaltung Ihrer Systeme vereinfachen können. Im Gegensatz zu anderen textbasierten Shells nutzt PowerShell die Leistung des .NET Frameworks und bietet damit umfangreiche Funktionen für die Steuerung Ihrer Windows-Umgebungen.
Quelle: MSDN: Microsoft PowerShell
 

PowerShell ISE

Die PowerShell Integrated Scripting Environment (ISE) ist eine Windows-Anwendung, die eine verbesserte Nutzung von PowerShell für Anfänger und Experten gleichermaßen unterstützt. Zu den zahlreichen Funktionen der ISE gehören:

  • Ein integrierter Editor zum Schreiben, Testen und Debuggen von Skripten
  • Vollständige IntelliSense-Tabulatur, Syntax-Hervorhebung und kontextsensitive Hilfe
  • Eine Vielzahl von Tastenkombinationen
  • Unterstützung für Rechts-nach-links-Sprachen
  • Erweiterbare Add-Ons (MSDN PowerShell Community)

Quelle: MSDN: Microsoft PowerShell
 

Konzeptioneller Beweiß (PoC)

Es gibt aktuell am Markt viele Möglichkeiten wie man sich mit dem Management Server, (per RQL) über die WebService API, unterhalten kann:

  • JavaScript
  • PHP
  • Java
  • .NET
  • usw.

Doch wie kommt man aus einer Shell an diesen WebService? Kann man auch noch direkt den vollen Funktionsumfang des Systems dabei nutzen? Yes we can! ;) ... Dazu habe ich ein paar Beispiele in Form einen Prototyps aufgebaut, welche in den nachfolgendne Abschnitten erläutert werden ;)
 

Globaler Sitzungsspeicher

Als erstes habe ich mir Gedanken gemacht, wie man häufig verwendete Daten während der gesamten PowerShell-Session einfach vorhalten kann. Dazu habe ich mir einen globalen Session-Zwischenspeicher ($Global:StoreRQL) angelegt:

 PowerShell 
# Create Global Value Storage
$Global:StoreRQL = New-Object PSCustomObject;
$Global:StoreRQL | Add-Member -Type NoteProperty -Name ("DebugMode") -Value ($true) -Force;
$Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Scriptname") -Value ($MyInvocation.MyCommand.Name) -Force;
$Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Timestamp") -Value ([int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds) -Force;


Damit man auch jederzeit, während der Entwicklung, sich den Inhalt des Session-Zwischenspeichers anzeigen lassen. Habe ich mir dazu eine kleine Funktion (Show-RQLStore) erstellt:

 PowerShell 
# Show RQL Store
Function Show-RQLStore () {
    If ($Global:StoreRQL.DebugMode) {
        If ($Global:StoreRQL.PSObject.Properties.Count -gt 0) {
            # Show RQL Store Properties
            $Global:StoreRQL | Select-Object * -ExcludeProperty ("Proxy", "ErrorA", "ResultA", "Password") | Format-List;
            #$Global:StoreRQL | Select-Object * -ExcludeProperty ("Proxy", "ErrorA", "ResultA") | Format-List;
            # Show RQL Errors
            If ($Global:StoreRQL.ErrorA.Value) {
                Write-Host ("ErrorA : " + $Global:StoreRQL.ErrorA.Value) -ForegroundColor Red;
                Break;
            }
            # Show RQL Results
            If ($Global:StoreRQL.ResultA.Value) {
                Write-Host ("ResultA : " + $Global:StoreRQL.ResultA.Value);
            }
        }
    }
}

 

Kleines Helferlein für Standardvariablen

Damit man sich nicht immer um häufig verwendete Standardwerte wie:

  • Sessionkey
  • LoginGUID
  • ProjektGUD

in den RQL-Requests kümmern muss. Erstellte auch hierfür ich mir eine kleine PowerShell-Funktion (ReplaceValuesRQL):

 PowerShell 
# Replace Values in RQL Request
Function ReplaceValuesRQL ($RQL) {
    $XML = [xml]$RQL;
    If($XML.IODATA.HasAttributes) {
        $XML.IODATA.loginguid = $Global:StoreRQL.LoginGUID;
        $XML.IODATA.sessionkey = $Global:StoreRQL.SessionKey;
    }
    If($XML.IODATA.HasChildNodes) {
        If($XML.IODATA.GetElementsByTagName("ADMINISTRATION").action -eq "login") {
            $XML.IODATA.ADMINISTRATION.name = $Global:StoreRQL.Username;
            $XML.IODATA.ADMINISTRATION.password = $Global:StoreRQL.Password;
        }
        If($XML.IODATA.GetElementsByTagName("ADMINISTRATION").action -eq "validate") {
            $XML.IODATA.ADMINISTRATION.guid = $Global:StoreRQL.LoginGUID;
            If($XML.IODATA.ADMINISTRATION.HasChildNodes) {
                If($XML.IODATA.ADMINISTRATION.GetElementsByTagName("PROJECT").HasAttributes) {
                    $XML.IODATA.ADMINISTRATION.PROJECT.guid = $Global:StoreRQL.ProjectGUID;
                }
            }
        }
        If($XML.IODATA.GetElementsByTagName("ADMINISTRATION").HasChildNodes) {
            if($XML.IODATA.ADMINISTRATION.GetElementsByTagName("LOGOUT").HasAttributes) {
                $XML.IODATA.ADMINISTRATION.LOGOUT.guid = $Global:StoreRQL.LoginGUID;
            }
        }
    }
    $RQL = $XML.OuterXml;
    Return $RQL;
}

 

WebService Verbindung vorbereiten

Damit man nun auch eine Verbindung zum RQL-WebService herstellen kann, habe ich mir die notwendingen Befehle in zwei weitere Funktion gepackt. Weobei sich eine um die Vorbereitung der Verbindung (RQL-OpenSession) kümmert:

 PowerShell 
# Open WebService Session
Function RQL-OpenSession () {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Open Session");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Proxy") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("ErrorA") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("ResultA") -Value ("") -Force;
    # Connect to WebService
    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};
    $Global:StoreRQL.Proxy = New-WebServiceProxy -Uri ('https://localhost/cms/WebService/RqlWebService.svc?WSDL');
    $Global:StoreRQL.ErrorA = [ref]$Global:StoreRQL.Proxy.value;
    $Global:StoreRQL.ResultA = [ref]$Global:StoreRQL.Proxy.value;
    # Display RQL Store Properties
    Show-RQLStore;
}


und eine andere Funktion dann auch wieder alles sauber (Session-Zwischenspeicher) aufräumt und schließt (RQL-CloseSession) :)

 PowerShell 
# Close WebService Session
Function RQL-CloseSession () {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Close Session");
    # Remove Properties from RQL Store
    $Global:StoreRQL.PSObject.Members | ForEach { $Global:StoreRQL.PsObject.Members.Remove($_.Name) }
    # Display RQL Store Properties
    Show-RQLStore;
}

 

WebService Login vorbereiten

Damit man nicht in jedem Skript in "Plaintext" das Password für den Login einpflegen muss, habe ich mir die PowerShell / .NET / Windows Funktionalitäten zu nutze gemacht. Dazu wird einfach ein verschlüsseltes Passwort aus einer Datei direkt von der Festplatte geladen (RQL-GetLoginData).

 PowerShell 
# Prepare Login
Function RQL-GetLoginData ($CredentialsUserName) {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Get Login Data");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Username") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Password") -Value ("") -Force;
    # Get Credentials
    #$CredentialsUserName = "wsm.powershell.account";
    $CredentialsPasswordFile = Get-Content ("C:\Source\PowerShell\Store\{0}.pwd" -f $CredentialsUserName) -ErrorAction Stop;
    $CredentialsSecurePassword = $CredentialsPasswordFile | ConvertTo-SecureString;
    $Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList ($CredentialsUserName, $CredentialsSecurePassword);
    # Store Values to RQL Store Properties
    $Global:StoreRQL.Username = $Credentials.GetNetworkCredential().Username;
    $Global:StoreRQL.Password = $Credentials.GetNetworkCredential().Password;
    # Display RQL Store Properties
    Show-RQLStore;
}


Alternativ kann man auch eine WinAuth Abmeldung vornehmen, dazu muss jedoch der Account unter dem die PowerShell läuft im AD-DS vorliegen. Ebenso muss der Management Server auch eine AD-DS Anbindung für den Login haben und dann (so die Idee) sollte es möglich sein, dass man per WinAuth sich direkt am RQL-WebService anmelden kann. Dazu aber in einem späteren Artikel mehr ;)

Hier noch ein weiteres PowerShell-Skript (Set-SecurePasswordFile.ps1), welches es ermöglicht die Passworte verschlüsselt auf die Festplatte in eine Datei zu schreiben:

 Set-SecurePasswordFile.ps1 
$CredentialsInput = Get-Credential -Message ("Please type the password for WSM Account:");
$CredentialsUserName = $CredentialsInput.GetNetworkCredential().Username;
$CredentialsPassword = $CredentialsInput.GetNetworkCredential().Password;
if ($CredentialsUserName) {
    $CredentialsSecureStringPassword = $CredentialsPassword | ConvertTo-SecureString -AsPlainText -Force;
    $Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList ($CredentialsUserName, $CredentialsSecureStringPassword);
    $secureStringOutPut = $CredentialsSecureStringPassword | ConvertFrom-SecureString;
    Set-Content ("C:\Source\PowerShell\Store\{0}.pwd" -f $CredentialsUserName) ($secureStringOutPut);
}


Um zu prüfen, ob ein Passwort auch korrekt abgespeichert wurde, gibt es noch ein weiteres Hilfs-Skript (Get-SecurePasswordFile.ps1):

 Get-SecurePasswordFile.ps1 
$CredentialsUserName = "wsm.powershell.account";
$CredentialsPasswordFile = Get-Content ("C:\Source\PowerShell\Store\{0}.pwd" -f $CredentialsUserName);
$CredentialsSecurePassword = $CredentialsPasswordFile | ConvertTo-SecureString;
$Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList ($CredentialsUserName, $CredentialsSecurePassword);
$PlainPassword = $Credentials.GetNetworkCredential().Password;
$Credentials | Format-List;
$PlainPassword;

 

Weitere Hilfsmittel für eine einfachere Entwicklung

Hierbei handelt es sich um weitere Funktionen, welche als Helferlein innerhalb der nachfolgenden RQL-PowerShell-Funktionen zum Einsatz kommen werden. Diesen ganzen Block einfach in eine separate PowerShell-Skriptdatei abspeichern — in diesem Fall habe ich die Datei Lib.ps1 genannt:

 Lib.ps1 
Function Pause ($Message = "Press any key to continue . . . ") {
    If ($psISE) {
        # The "ReadKey" functionality is not supported in Windows PowerShell ISE.
        $Shell = New-Object -ComObject "WScript.Shell";
        $Button = $Shell.Popup("Click OK to continue.", 0, "Script Paused", 0);
        Return;
    }
    Write-Host -NoNewline $Message;
    $Ignore =
        16,  # Shift (left or right)
        17,  # Ctrl (left or right)
        18,  # Alt (left or right)
        20,  # Caps lock
        91,  # Windows key (left)
        92,  # Windows key (right)
        93,  # Menu key
        144, # Num lock
        145, # Scroll lock
        166, # Back
        167, # Forward
        168, # Refresh
        169, # Stop
        170, # Search
        171, # Favorites
        172, # Start/Home
        173, # Mute
        174, # Volume Down
        175, # Volume Up
        176, # Next Track
        177, # Previous Track
        178, # Stop Media
        179, # Play
        180, # Mail
        181, # Select Media
        182, # Application 1
        183  # Application 2
 
    While ($KeyInfo.VirtualKeyCode -Eq $Null -Or $Ignore -Contains $KeyInfo.VirtualKeyCode) {
        $KeyInfo = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown");
    }
    Write-Host;
}

#Function Pause($M="Press any key to continue . . . "){If($psISE){$S=New-Object -ComObject "WScript.Shell";$B=$S.Popup("Click OK to continue.",0,"Script Paused",0);Return};Write-Host -NoNewline $M;$I=16,17,18,20,91,92,93,144,145,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183;While($K.VirtualKeyCode -Eq $Null -Or $I -Contains $K.VirtualKeyCode){$K=$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")};Write-Host}


Function Write-CHost($message = ""){
    [string]$pipedMessage = @($Input)
    If (!$message)
    {  
        If ( $pipedMessage ) {
            $message = $pipedMessage
        }
    }
	If ( $message ){
		# predefined Color Array
		$colors = @("black","blue","cyan","darkblue","darkcyan","darkgray","darkgreen","darkmagenta","darkred","darkyellow","gray","green","magenta","red","white","yellow");

		# Get the default Foreground Color
		$defaultFGColor = "white";
        
		# Set CurrentColor to default Foreground Color
		$CurrentColor = $defaultFGColor

		# Split Messages
		$message = $message.Split("#")

		# Iterate through splitted array
		ForEach( $RQL in $message ){
			# If a string between #-Tags is equal to any predefined color, and is equal to the defaultcolor: set current color
			If ( $colors -contains $RQL.ToLower() -and $CurrentColor -eq $defaultFGColor ){
				$CurrentColor = $RQL          
			} Else {
				# If string is a output message, than write string with current color (with no line break)
                If ( $CurrentColor -eq "green" ) {
                    #$RQL = "`r`n`t" + $RQL;
                }
				Write-Host -NoNewline -ForegroundColor $CurrentColor $RQL
				# Reset current color
				$CurrentColor = $defaultFGColor
			}
			# Write Empty String at the End
		}
		# Single write-host for the final line break
		Write-Host
	}
}


Function WriteXmlToScreen ([xml]$Xml, $Indent=2)
{
    If ($Global:StoreRQL.DebugMode) {
        $StringWriter = New-Object System.IO.StringWriter;
        $XmlWriter = New-Object System.Xml.XmlTextWriter $StringWriter;
        $XmlWriter.Formatting = "indented";
        $xmlWriter.Indentation = $Indent;
        $Xml.WriteTo($XmlWriter);
        $XmlWriter.Flush();
        $StringWriter.Flush();
        Write-Output $StringWriter.ToString();
    }
}


Function WriteRQLToScreen ([xml]$Xml, $Indent=4)
{
    If ($Global:StoreRQL.DebugMode) {
        $StringWriter = New-Object System.IO.StringWriter;
        $XmlWriter = New-Object System.Xml.XmlTextWriter $StringWriter;
        $XmlWriter.Formatting = "indented";
        $xmlWriter.Indentation = $Indent;
        $xml.WriteTo($XmlWriter);
        $XmlWriter.Flush();
        $StringWriter.Flush();
        #Write-Output $StringWriter.ToString();
        $string = $StringWriter.ToString();
        $string = $string.Replace("IODATA", "#blue#IODATA#white#");
        $string = $string.Replace("ADMINISTRATION ", "#red#ADMINISTRATION#white# ");
        $string = $string.Replace("ELEMENT ", "#red#ELEMENT#white# ");
        $string = $string.Replace("ELEMENTS ", "#yellow#ELEMENTS#white# ");
        $string = $string.Replace("LASTMODULES", "#yellow#LASTMODULES#white#");
        $string = $string.Replace("<LICENSE", "<#red#LICENSE#white# ");
        $string = $string.Replace("LINK", "#red#LINK#white#");
        $string = $string.Replace("<LOGIN ", "<#red#LOGIN#white# ");
        $string = $string.Replace("<MODULE ", "<#red#MODULE#white# ");
        $string = $string.Replace("/MODULE>", "/<#red#MODULE#white#>");
        $string = $string.Replace("<MODULES", "<#yellow#MODULES#white#");
        $string = $string.Replace("/MODULES>", "/#yellow#MODULES#white#>");
        $string = $string.Replace("<PAGE", "<#red#PAGE#white#");
        $string = $string.Replace("/PAGE>", "/#red#PAGE#white#>");
        $string = $string.Replace("PAGES", "#yellow#PAGES#white#");
        $string = $string.Replace("PROJECT ", "#red#PROJECT#white# ");
        $string = $string.Replace("SERVER ", "#red#SERVER#white# ");
        $string = $string.Replace("USER ", "#red#USER#white# ");
        $string = $string.Replace("/USER", "/#red#USER#white#");
        $string = $string.Replace("/>", "#red#/#white#>");
        $string = $string.Replace(" action=", " #green#action=#white#");
        $string = $string.Replace(" guid=", " #green#guid=#white#");
        $string = $string.Replace(" loginguid=", " #green#loginguid=#white#");
        $string = $string.Replace(" id=", " #green#id=#white#");
        $string = $string.Replace(" name=", " #green#name=#white#");
        $string = $string.Replace(" fullname=", " #green#fullname=#white#");
        $string = $string.Replace(" linkguid=", " #green#linkguid=#white#");
        $string = $string.Replace(" editlinkguid=", " #green#editlinkguid=#white#");
        $string = $string.Replace(" targetlinkguid=", " #green#targetlinkguid=#white#");
        $string = $string.Replace(" pageguid=", " #green#pageguid=#white#");
        $string = $string.Replace(" parentguid=", " #green#parentguid=#white#");
        $string = $string.Replace(" project=", " #green#project=#white#");
        $string = $string.Replace(" projectguid=", " #green#projectguid=#white#");
        $string = $string.Replace(" projectname=", " #green#projectname=#white#");
        $string = $string.Replace(" last=", " #green#last=#white#");
        $string = $string.Replace(" key=", " #green#key=#white#");
        $string = $string.Replace(" sessionkey=", " #green#sessionkey=#white#");
        $string = $string.Replace(" userguid=", " #green#userguid=#white#");
        $string = $string.Replace(" userid=", " #green#userid=#white#");
        $string = $string.Replace(" server=", " #green#server=#white#");
        $string = $string.Replace(" serverguid=", " #green#serverguid=#white#");
        $string = $string.Replace(" userkey=", " #green#userkey=#white#");
        $string = $string.Replace(" usertoken=", " #green#usertoken=#white#");
        $string = $string.Replace(" eltname=", " #green#eltname=#white#");
        $string = $string.Replace(" aliasname=", " #green#aliasname=#white#");
        $string = $string.Replace(" value=", " #green#value=#white#");
        $string = $string.Replace(" status=", " #green#status=#white#");
        $string = $string.Replace(" variable=", " #green#variable=#white#");
        $string = $string.Replace(" headline=", " #green#headline=#white#");
        $string = $string.Replace(" type=", " #green#type=#white#");
        $string = $string.Replace(" elttype=", " #green#elttype=#white#");
        $string = $string.Replace(" flags=", " #green#flags=#white#");
        $string = $string.Replace(" eltflags=", " #green#eltflags=#white#");
        $string = $string.Replace(" islink=", " #green#islink=#white#");
        $string = $string.Replace(" level=", " #green#level=#white#");
        $string = $string.Replace(" orderid=", " #green#orderid=#white#");
        $string = $string.Replace(" languageid=", " #green#languageid=#white#");
        $string = $string.Replace(" languagevariantid=", " #green#languagevariantid=#white#");
        $string = $string.Replace(" languagevariantguid=", " #green#languagevariantguid=#white#");
        $string = $string.Replace(" mainlanguagevariantid=", " #green#mainlanguagevariantid=#white#");
        $string = $string.Replace(" mainlanguagevariantguid=", " #green#mainlanguagevariantguid=#white#");
        $string = $string.Replace(" dialoglanguageid=", " #green#dialoglanguageid=#white#");
        $string = $string.Replace(" threadguid=", " #green#threadguid=#white#");
        $string = $string.Replace(" elementguid=", " #green#elementguid=#white#");
        $string = $string.Replace(" mainlinkguid=", " #green#mainlinkguid=#white#");
        $string = $string.Replace(" templateguid=", " #green#templateguid=#white#");
        $string = $string.Replace(" templateelementguid=", " #green#templateelementguid=#white#");
        $string = $string.Replace(" eltrequired=", " #green#eltrequired=#white#");
        $string = $string.Replace(" flags1=", " #green#flags1=#white#");
        $string = $string.Replace(" flags2=", " #green#flags2=#white#");
        $string = $string.Replace(" rights1=", " #green#rights1=#white#");
        $string = $string.Replace(" rights2=", " #green#rights2=#white#");
        $string = $string.Replace(" rights3=", " #green#rights3=#white#");
        $string = $string.Replace(" rights4=", " #green#rights4=#white#");
        $string = $string.Replace(" navigationposition=", " #green#navigationposition=#white#");
        write-chost $string;
    }
}

 

Jetzt wird es ernst :)

Nun widmen wir uns den RQL-Anfragen und schicken diese per PowerShell an den Management Server. Als erstes die PowerShell-Funktion für den Login (RQL-Login):

 PowerShell 
# Login
Function RQL-Login () {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Login");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("UserGUID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("LoginGUID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("SessionKey") -Value ("") -Force;
    # Prepare RQL Request
    $RQL = ("<IODATA><ADMINISTRATION action='login' name='[!username!]' password='[!password!]'/></IODATA>");
    $RQL = ReplaceValuesRQL ($RQL);
    # Send RQL Request to WebService
    [xml]$Response = $Global:StoreRQL.Proxy.Execute($RQL, $Global:StoreRQL.ErrorA, $Global:StoreRQL.ResultA);
    # Store Results to RQL Store Properties
    $Global:StoreRQL.UserGuid = $Response.IODATA.USER.guid;
    $Global:StoreRQL.LoginGuid = $Response.IODATA.LOGIN.guid;
    $Global:StoreRQL.SessionKey = $Response.IODATA.LOGIN.guid;
    # Display RQL Response Object
    If ($Global:StoreRQL.DebugMode) {
        $Response.ChildNodes | Format-List;
    }
    # Display RQL Response String
    WriteRQLToScreen $Response.OuterXml.ToString();
    # Display RQL Store Properties
    Show-RQLStore;
}


Als nächstes geht es um die Funktion für den Projekt-Zugriff (RQL-ConntectToProject):

 PowerShell 
# Connect to project
Function RQL-ConntectToProject ($ProjectGUID) {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Connect to Project");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("ProjectGUID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("ServerGUID") -Value ("") -Force;
    # Store Values to RQL Store Properties
    $Global:StoreRQL.ProjectGuid = ($ProjectGUID);
    # Prepare RQL Request
    $RQL = ("<IODATA loginguid='[!guid_login!]' sessionkey='[!key!]'><ADMINISTRATION action='validate' guid='[!guid_login!]' useragent='script'><PROJECT guid='[!guid_project!]'/></ADMINISTRATION></IODATA>");
    $RQL = ReplaceValuesRQL ($RQL);
    # Send RQL Request to WebService
    [xml]$Response = $Global:StoreRQL.Proxy.Execute($RQL, $Global:StoreRQL.ErrorA, $Global:StoreRQL.ResultA);
    # Store Results to RQL Store Properties
    $Global:StoreRQL.ServerGUID = $Response.IODATA.SERVER.guid;
    # Display RQL Response Object
    If ($Global:StoreRQL.DebugMode) {
        $Response.ChildNodes | Format-List;
    }
    # Display RQL Response String
    WriteRQLToScreen $Response.OuterXml.ToString();
    # Display RQL Store Properties
    Show-RQLStore;
}


Nun kommen wir zu einem weiteren RQL-Request (RQL-CreateConnectPage) für unseren Prototypen:

 PowerShell 
# Create and Connect Page
Function RQL-CreateConnectPage ($TemplateGUID, $LinkGUID) {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Create and Connect Page");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("PageGUID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("PageID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("LinkGUID") -Value ($LinkGUID) -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("TemplateGUID") -Value ($TemplateGUID) -Force;
    # Prepare RQL Request
    $RQL = ("<IODATA loginguid='[!guid_login!]' sessionkey='[!key!]'><LINK action='assign' guid='[!guid_link!]'><PAGE action='addnew' templateguid='[!guid_template!]'/></LINK></IODATA>");
    $RQL = $RQL.Replace("[!guid_template!]", $TemplateGUID).Replace("[!guid_link!]", $LinkGUID);
    $RQL = ReplaceValuesRQL ($RQL);
    # Send RQL Request to WebService
    [xml]$Response = $Global:StoreRQL.Proxy.Execute($RQL, $Global:StoreRQL.ErrorA, $Global:StoreRQL.ResultA);
    # Store Results to RQL Store Properties
    $Global:StoreRQL.PageGUID = $Response.IODATA.LINK.PAGE.guid;
    $Global:StoreRQL.PageID = $Response.IODATA.LINK.PAGE.id;
    # Display RQL Response Object
    If ($Global:StoreRQL.DebugMode) {
        $Response.ChildNodes | Format-List;
    }
    # Display RQL Response String
    WriteRQLToScreen $Response.OuterXml.ToString();
    # Display RQL Store Properties
    Show-RQLStore;
    If ($Global:StoreRQL.ErrorA.Value) {
        $Global:StoreRQL | Select-Object ErrorA.Value | Format-List;
    }
    #$Global:StoreRQL.ResultA.Value;
}


Zum Schluss noch der Logout, damit man die Verbindung auch korrekt beendet (RQL-Logout):

 PowerShell 
# Logout
Function RQL-Logout () {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Logout");
    # Prepare RQL Request
    $RQL = ("<IODATA loginguid='[!guid_login!]' sessionkey='[!key!]'><ADMINISTRATION><LOGOUT guid='[!guid_login!]'/></ADMINISTRATION></IODATA>");
    $RQL = ReplaceValuesRQL ($RQL);
    # Send RQL Request to WebService
    [xml]$Response = $Global:StoreRQL.Proxy.Execute($RQL, $Global:StoreRQL.ErrorA, $Global:StoreRQL.ResultA);
    # Display RQL Response Object
    If ($Global:StoreRQL.DebugMode) {
        $Response | Format-List;
    }
    # Display RQL Response String
    WriteRQLToScreen $Response.OuterXml.ToString();
    # Remove Properties from RQL Store
    $Global:StoreRQL.PSObject.Members | Where-Object {$_.Name -like ("*GUID") -or $_.Name -like ("*ID") -or $_.Name -like ("*Key")} | ForEach { $Global:StoreRQL.PsObject.Members.Remove($_.Name) }
    # Display RQL Store Properties
    Show-RQLStore;
}

 

Befehlskette für den PoC

Hier nun ein Beispiel für ein Step-by-Step der PS/RQL-Befehle:

 PowerShell 
$Global:StoreRQL.DebugMode = $true;

RQL-OpenSession;
Pause;
Write-Host ("---------------");

RQL-GetLoginData ("wsm.powershell.account");
Pause;
Write-Host ("---------------");

RQL-Login;
Pause;
Write-Host ("---------------");

RQL-ConntectToProject -ProjectGUID ("BC55EB11F6FA4A77884AB1872D82FDC9");
Pause;
Write-Host ("---------------");

RQL-CreateConnectPage -TemplateGUID ("D2797377A4DA438D836E142893B5BB64") -LinkGUID ("374B05435E964819A1181963D895AFBD");
Pause;
Write-Host ("---------------");

RQL-Logout;
Pause;
Write-Host ("---------------");

RQL-CloseSession;
Write-Host ("---------------");


und hier nun die kompakte Form (Debug-Mode on):

 PowerShell 
$Global:StoreRQL.DebugMode = $true;
#$Global:StoreRQL.DebugMode = $false;
RQL-OpenSession;
RQL-GetLoginData ("wsm.powershell.account");
RQL-Login;
RQL-ConntectToProject -ProjectGUID ("BC55EB11F6FA4A77884AB1872D82FDC9");
RQL-CreateConnectPage -TemplateGUID ("D2797377A4DA438D836E142893B5BB64") -LinkGUID ("374B05435E964819A1181963D895AFBD");
RQL-Logout;
RQL-CloseSession;


Zum Schluss nun noch ein Beispiel für einen Massenablauf (Debug-Mode off):

 PowerShell 
$Global:StoreRQL.DebugMode = $false;
#$Global:StoreRQL.DebugMode = $true;
RQL-OpenSession;
RQL-GetLoginData ("wsm.powershell.account");
RQL-Login;
RQL-ConntectToProject -ProjectGUID ("BC55EB11F6FA4A77884AB1872D82FDC9");
For($i=1; $i -le 10; $i++){
    Write-Host $i " - " -NoNewline;
    RQL-CreateConnectPage -TemplateGUID ("D2797377A4DA438D836E142893B5BB64") -LinkGUID ("374B05435E964819A1181963D895AFBD");
}
RQL-Logout;
RQL-CloseSession;

 

Testlauf

Jetzt haben wir alles wichtige zusammen und können den ersten Testlauf mit dem finalen PowerShell-Skript (RQL-Example.ps1) durchführen:

 RQL-Example.ps1 
. "C:\Source\PowerShell\Lib.ps1";


# Replace Values in RQL Request
Function ReplaceValuesRQL ($RQL) {
    $XML = [xml]$RQL;
    If($XML.IODATA.HasAttributes) {
        $XML.IODATA.loginguid = $Global:StoreRQL.LoginGUID;
        $XML.IODATA.sessionkey = $Global:StoreRQL.SessionKey;
    }
    If($XML.IODATA.HasChildNodes) {
        If($XML.IODATA.GetElementsByTagName("ADMINISTRATION").action -eq "login") {
            $XML.IODATA.ADMINISTRATION.name = $Global:StoreRQL.Username;
            $XML.IODATA.ADMINISTRATION.password = $Global:StoreRQL.Password;
        }
        If($XML.IODATA.GetElementsByTagName("ADMINISTRATION").action -eq "validate") {
            $XML.IODATA.ADMINISTRATION.guid = $Global:StoreRQL.LoginGUID;
            If($XML.IODATA.ADMINISTRATION.HasChildNodes) {
                If($XML.IODATA.ADMINISTRATION.GetElementsByTagName("PROJECT").HasAttributes) {
                    $XML.IODATA.ADMINISTRATION.PROJECT.guid = $Global:StoreRQL.ProjectGUID;
                }
            }
        }
        If($XML.IODATA.GetElementsByTagName("ADMINISTRATION").HasChildNodes) {
            if($XML.IODATA.ADMINISTRATION.GetElementsByTagName("LOGOUT").HasAttributes) {
                $XML.IODATA.ADMINISTRATION.LOGOUT.guid = $Global:StoreRQL.LoginGUID;
            }
        }
    }
    $RQL = $XML.OuterXml;
    Return $RQL;
}


# Create Global Value Storage
$Global:StoreRQL = New-Object PSCustomObject;
$Global:StoreRQL | Add-Member -Type NoteProperty -Name ("DebugMode") -Value ($true) -Force;
$Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Scriptname") -Value ($MyInvocation.MyCommand.Name) -Force;
$Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Timestamp") -Value ([int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds) -Force;


# Show RQL Store
Function Show-RQLStore () {
    If ($Global:StoreRQL.DebugMode) {
        If ($Global:StoreRQL.PSObject.Properties.Count -gt 0) {
            # Show RQL Store Properties
            $Global:StoreRQL | Select-Object * -ExcludeProperty ("Proxy", "ErrorA", "ResultA", "Password") | Format-List;
            #$Global:StoreRQL | Select-Object * -ExcludeProperty ("Proxy", "ErrorA", "ResultA") | Format-List;
            # Show RQL Errors
            If ($Global:StoreRQL.ErrorA.Value) {
                Write-Host ("ErrorA : " + $Global:StoreRQL.ErrorA.Value) -ForegroundColor Red;
                Break;
            }
            # Show RQL Results
            If ($Global:StoreRQL.ResultA.Value) {
                Write-Host ("ResultA : " + $Global:StoreRQL.ResultA.Value);
            }
        }
    }
}


# Open WebService Session
Function RQL-OpenSession () {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Open Session");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Proxy") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("ErrorA") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("ResultA") -Value ("") -Force;
    # Connect to WebService
    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};
    $Global:StoreRQL.Proxy = New-WebServiceProxy -Uri ('https://localhost/cms/WebService/RqlWebService.svc?WSDL');
    $Global:StoreRQL.ErrorA = [ref]$Global:StoreRQL.Proxy.value;
    $Global:StoreRQL.ResultA = [ref]$Global:StoreRQL.Proxy.value;
    # Display RQL Store Properties
    Show-RQLStore;
}


# Close WebService Session
Function RQL-CloseSession () {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Close Session");
    # Remove Properties from RQL Store
    $Global:StoreRQL.PSObject.Members | ForEach { $Global:StoreRQL.PsObject.Members.Remove($_.Name) }
    # Display RQL Store Properties
    Show-RQLStore;
}


# Prepare Login
Function RQL-GetLoginData ($CredentialsUserName) {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Get Login Data");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Username") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("Password") -Value ("") -Force;
    # Get Credentials
    #$CredentialsUserName = "wsm.powershell.account";
    $CredentialsPasswordFile = Get-Content ("C:\Source\PowerShell\Store\{0}.pwd" -f $CredentialsUserName) -ErrorAction Stop;
    $CredentialsSecurePassword = $CredentialsPasswordFile | ConvertTo-SecureString;
    $Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList ($CredentialsUserName, $CredentialsSecurePassword);
    # Store Values to RQL Store Properties
    $Global:StoreRQL.Username = $Credentials.GetNetworkCredential().Username;
    $Global:StoreRQL.Password = $Credentials.GetNetworkCredential().Password;
    # Display RQL Store Properties
    Show-RQLStore;
}


# Login
Function RQL-Login () {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Login");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("UserGUID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("LoginGUID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("SessionKey") -Value ("") -Force;
    # Prepare RQL Request
    $RQL = ("<IODATA><ADMINISTRATION action='login' name='[!username!]' password='[!password!]'/></IODATA>");
    $RQL = ReplaceValuesRQL ($RQL);
    # Send RQL Request to WebService
    [xml]$Response = $Global:StoreRQL.Proxy.Execute($RQL, $Global:StoreRQL.ErrorA, $Global:StoreRQL.ResultA);
    # Store Results to RQL Store Properties
    $Global:StoreRQL.UserGuid = $Response.IODATA.USER.guid;
    $Global:StoreRQL.LoginGuid = $Response.IODATA.LOGIN.guid;
    $Global:StoreRQL.SessionKey = $Response.IODATA.LOGIN.guid;
    # Display RQL Response Object
    If ($Global:StoreRQL.DebugMode) {
        $Response.ChildNodes | Format-List;
    }
    # Display RQL Response String
    WriteRQLToScreen $Response.OuterXml.ToString();
    # Display RQL Store Properties
    Show-RQLStore;
}


# Connect to project
Function RQL-ConntectToProject ($ProjectGUID) {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Connect to Project");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("ProjectGUID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("ServerGUID") -Value ("") -Force;
    # Store Values to RQL Store Properties
    $Global:StoreRQL.ProjectGuid = ($ProjectGUID);
    # Prepare RQL Request
    $RQL = ("<IODATA loginguid='[!guid_login!]' sessionkey='[!key!]'><ADMINISTRATION action='validate' guid='[!guid_login!]' useragent='script'><PROJECT guid='[!guid_project!]'/></ADMINISTRATION></IODATA>");
    $RQL = ReplaceValuesRQL ($RQL);
    # Send RQL Request to WebService
    [xml]$Response = $Global:StoreRQL.Proxy.Execute($RQL, $Global:StoreRQL.ErrorA, $Global:StoreRQL.ResultA);
    # Store Results to RQL Store Properties
    $Global:StoreRQL.ServerGUID = $Response.IODATA.SERVER.guid;
    # Display RQL Response Object
    If ($Global:StoreRQL.DebugMode) {
        $Response.ChildNodes | Format-List;
    }
    # Display RQL Response String
    WriteRQLToScreen $Response.OuterXml.ToString();
    # Display RQL Store Properties
    Show-RQLStore;
}


# Create and Connect Page
Function RQL-CreateConnectPage ($TemplateGUID, $LinkGUID) {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Create and Connect Page");
    # Create New Properties in RQL Store
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("PageGUID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("PageID") -Value ("") -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("LinkGUID") -Value ($LinkGUID) -Force;
    $Global:StoreRQL | Add-Member -Type NoteProperty -Name ("TemplateGUID") -Value ($TemplateGUID) -Force;
    # Prepare RQL Request
    $RQL = ("<IODATA loginguid='[!guid_login!]' sessionkey='[!key!]'><LINK action='assign' guid='[!guid_link!]'><PAGE action='addnew' templateguid='[!guid_template!]'/></LINK></IODATA>");
    $RQL = $RQL.Replace("[!guid_template!]", $TemplateGUID).Replace("[!guid_link!]", $LinkGUID);
    $RQL = ReplaceValuesRQL ($RQL);
    # Send RQL Request to WebService
    [xml]$Response = $Global:StoreRQL.Proxy.Execute($RQL, $Global:StoreRQL.ErrorA, $Global:StoreRQL.ResultA);
    # Store Results to RQL Store Properties
    $Global:StoreRQL.PageGUID = $Response.IODATA.LINK.PAGE.guid;
    $Global:StoreRQL.PageID = $Response.IODATA.LINK.PAGE.id;
    # Display RQL Response Object
    If ($Global:StoreRQL.DebugMode) {
        $Response.ChildNodes | Format-List;
    }
    # Display RQL Response String
    WriteRQLToScreen $Response.OuterXml.ToString();
    # Display RQL Store Properties
    Show-RQLStore;
    If ($Global:StoreRQL.ErrorA.Value) {
        $Global:StoreRQL | Select-Object ErrorA.Value | Format-List;
    }
    #$Global:StoreRQL.ResultA.Value;
}


# Logout
Function RQL-Logout () {
    $Global:StoreRQL.Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalMilliseconds;
    Write-Host ("ACTION: Logout");
    # Prepare RQL Request
    $RQL = ("<IODATA loginguid='[!guid_login!]' sessionkey='[!key!]'><ADMINISTRATION><LOGOUT guid='[!guid_login!]'/></ADMINISTRATION></IODATA>");
    $RQL = ReplaceValuesRQL ($RQL);
    # Send RQL Request to WebService
    [xml]$Response = $Global:StoreRQL.Proxy.Execute($RQL, $Global:StoreRQL.ErrorA, $Global:StoreRQL.ResultA);
    # Display RQL Response Object
    If ($Global:StoreRQL.DebugMode) {
        $Response | Format-List;
    }
    # Display RQL Response String
    WriteRQLToScreen $Response.OuterXml.ToString();
    # Remove Properties from RQL Store
    $Global:StoreRQL.PSObject.Members | Where-Object {$_.Name -like ("*GUID") -or $_.Name -like ("*ID") -or $_.Name -like ("*Key")} | ForEach { $Global:StoreRQL.PsObject.Members.Remove($_.Name) }
    # Display RQL Store Properties
    Show-RQLStore;
}


# ------


$Global:StoreRQL.DebugMode = $true;

RQL-OpenSession;
Pause;
Write-Host ("---------------");

RQL-GetLoginData ("wsm.powershell.account");
Pause;
Write-Host ("---------------");

RQL-Login;
Pause;
Write-Host ("---------------");

RQL-ConntectToProject -ProjectGUID ("BC55EB11F6FA4A77884AB1872D82FDC9");
Pause;
Write-Host ("---------------");

RQL-CreateConnectPage -TemplateGUID ("D2797377A4DA438D836E142893B5BB64") -LinkGUID ("374B05435E964819A1181963D895AFBD");
Pause;
Write-Host ("---------------");

RQL-Logout;
Pause;
Write-Host ("---------------");

RQL-CloseSession;
Write-Host ("---------------");



# ------


<#
$Global:StoreRQL.DebugMode = $false;
#$Global:StoreRQL.DebugMode = $true;
RQL-OpenSession;
RQL-GetLoginData ("wsm.powershell.account");
RQL-Login;
RQL-ConntectToProject -ProjectGUID ("BC55EB11F6FA4A77884AB1872D82FDC9");
RQL-CreateConnectPage -TemplateGUID ("D2797377A4DA438D836E142893B5BB64") -LinkGUID ("374B05435E964819A1181963D895AFBD");
RQL-Logout;
RQL-CloseSession;
#>


# ------


<#
$Global:StoreRQL.DebugMode = $false;
#$Global:StoreRQL.DebugMode = $true;
RQL-OpenSession;
RQL-GetLoginData ("wsm.powershell.account");
RQL-Login;
RQL-ConntectToProject -ProjectGUID ("BC55EB11F6FA4A77884AB1872D82FDC9");
For($i=1; $i -le 10; $i++){
    Write-Host $i " - " -NoNewline;
    RQL-CreateConnectPage -TemplateGUID ("D2797377A4DA438D836E142893B5BB64") -LinkGUID ("374B05435E964819A1181963D895AFBD");
}
#RQL-Logout;
#RQL-CloseSession;
#>


Fazit

Der o.g. Prototyp ist nicht perfekt und kann bestimmt optimiert bzw. verbessert werden. Die Idee für diesen Artikel war aufzuzeigen, dass man auch per PowerShell mit dem Management Server (per RQL) sprechen kann. Wichtig hierbei ist, dass man versteht wie man die RQL's an den Management-Server per PowerShell schickt. Und wie man mit den Rückmeldungen umgeht bzw. wie man sich häufig verwendete Daten zwischenspeichert.

Die Idee einen PowerShell-Skript-Sitzungs-Zwischenspeicher ($Global:StoreRQL) zu erstellen, ist eine von vielen Möglichkeiten um sich das Leben innerhalb der Skripte leichter zu machen. Für die kommenden Monate wird es immer wieder Skript-Beispiele, Erweiterungen und Artikel zu dem Thema geben.

Mein persönliches mittelfristiges Ziel ist, ein schickes kompaktes PowerShell2RQL-Framework zu entwickeln. Somit hat man dann auch aus einer OfficeIT-Sicht, die Möglichkeit den Management-Server optimal anbinden, einbinden und auch zu nutzen ;)


Weitere nützliche Informationen: