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:
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.
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
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:
Quelle: MSDN: Microsoft PowerShell
Es gibt aktuell am Markt viele Möglichkeiten wie man sich mit dem Management Server, (per RQL) über die WebService API, unterhalten kann:
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 ;)
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); } } } }
Damit man sich nicht immer um häufig verwendete Standardwerte wie:
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; }
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; }
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;
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; } }
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; }
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;
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; #>
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:
... ist Senior Site Reliability Engineer bei der Vodafone GmbH in Düsseldorf. Seit dem Jahr 2007 betreut er zusammen mit seinen Kollegen die OpenText- (vormals RedDot-) Plattform Web Site Management für die deutsche Konzernzentrale.
Er entwickelt Erweiterungen in Form von Plug-Ins und PowerShell Skripten. Seit den Anfängen in 2001 (RedDot CMS 4.0) kennt er sich speziell mit der Arbeitweise und den Funktionen des Management Server aus.