PowerShell: DSC Custom Resource
IMO the DSC information out there right now is kind of all over the place, and worse some of it is outdated, so I'm going to collect a few guides here, starting a bit out of order with a guide on how to write your own custom DSC resource. Later I'll put one together for configuring a DSC Pull server and then one on how to configure a client for DSC Pull operations.
First things first, you need this: xDscResourceDesigner
We wont be using any of the other functionality of it, but it at the very least is handy for creating the initial structure. Extract it like it says but most likely depending on your setup you are going to need to teomporarily run with an Unrestricted ExecutionPolicy. I couldn't unflag it to save my life and frankly didn't spend much time on it, remember to set your policy back after using it and there are no worries.
Anyway, the first thing you need to know is, what do you need to know? We are going to create something really simple here, a DSC Resource to make sure a file exists and set its contents. So we need a Path and Contents, then we need the mandatory Ensure variable, so our first bit of code is going to look like this:
What is essentially going on here is we are setting up our variables and then building the folder structure and MOF for our custom resource. Saving it all to our PSModules path so once you run this command you should be able to Get-DscResource and see zTest at the bottom of the list.
Now we have a script to modify, this script is located in the DSCResources sub-folder in the path we just specified. There are a couple things to know about this:
The Test Phase
This is where LCM looks to validate the state of the asset you are trying to configure. In our case, does this file exist? And what are its contents? So the content of your Test-TargetResource script will probably look a little something like:
if((Test-Path $Path) -eq $true){ if((Get-Content $Path) -eq $Content){ $return = $true }else{ $return = $false }else{ $return = $false }
That is pretty messy and all one line but you get the idea, there is no logic built in, you need to provide all the logic. Whether that be ensuring that you don't just assume that because the file exists it must have the right contents, or the logic to ensure that if $Ensure is set to "Absent" that you are removing the file thoroughly, handling errors, not outputting to host, etc. etc.
So assuming you do not have the file already and we run this resource, it will run through the Test-TargetResource and return $false, at which point if you set Ensure="Absent" it will be done, if you set it to "Present" it will move on to Set-TargetResource. Which will probably look something like this:
if($Ensure -eq "Present") { New-Item -Path $Path -ItemType File -Force Set-Content -Path $Path -Value $Content }elseif($Ensure -eq "Absent") { Remove-Item -Path $Path -Force -Confirm:$false }
Again, this is quick and dirty and not meant to represent the most robust function possible for creating/populating a file or deleting it thoroughly. Liberal use of try/catch and thinking about ways it could go wrong (including doing a little of your own validation to make sure what you just did took hold) are recommended.
The Test function MUST return $true or $false, Set does not need to (should not?) return anything at all, which leaves: Get-TargetResource
For now I'm not going to go too far into this, I'll just say that in reality this appears to be an informational function that allows you to pull information about the asset in question without actually doing anything. It MUST return a hastable of information about the object (usually the same information, sans Ensure, that you provide to the script). i.e.
$returnVal = @{ Path = $Path Content = (Get-Content -Path $Path) Ensure = $Ensure }
Again how strictly this is enforced and how much is really needed...in this example I would almost say not at all. You could just as easily replace $Path with (Test-Path -Path $Path) and drop Ensure entirely I think. I haven't tested the boundaries of how strictly you have to adhere to input->outputin this case.
Once all of this is done you should be able to do the following and get a usable mof ready to be executed:
Configuration Test { Import-DscResource -Module zTest Node "localhost" { zTest Testing { Ensure = "Present" Path = "C:\test.txt" Content = "Hello (automated) World!" } } } $output = "C:\\ " Test -OutputPath $output Start-DscConfiguration -Verbose -Wait -Force -Path $output
Please note that the OutputPath argument isn't required, without it the script will just spit out a mof in, well in this case it will create a folder called Test in whatever folder you are currently in and put it in there. I like to specify it so I know where my mof's are. Once you call it with that final command you should see LCM spit out a bunch of information regarding the process and any errors will show up there as well as in the Event Viewer.
Please note that a well written DSC Resource should be able to be run a million times and only touch something if it isn't in compliance. Meaning that if the script doesn't detect the file is missing or the contents are different from what they should be, it should effectively do nothing at all. It shouldn't set it anyway just to be sure, it shouldn't assume, it shouldn't cut corners, or hope and pray. The entire point here is to ensure consistent state accross an environment. The better your script is, the more reliable, and resilient, forward thinking and robust, the better your resource will be.
In the case of this Resource, if I planned on taking it farther I would probably make sure I had rights to the file before messing with it, include a try/catch around file creation and content placement, I would check the file existed once more with a fresh piece of memory (if you end up setting the content to a variable) and validate the contents afterwards as well. You could build in the option to allow forcing a specific encoding type to convert between UTF-32 and UTF-8 or ASCII if you wanted, provide options for Base64 input encoding (in case you wanted to handle script strings for instance, or you could make sure to escape and trim as needed) etc. etc.
The point being, it is easy to write something quick that will rather blindly carry out an action. But in the long run you need something that is going to be feature rich and robust to match.
And don't forget, if you don't copy the folder structure to the client machines, they wont have access to your Resource. The path we created this in on our workstation is the same path it needs to live on every other machine, and once it does, they too will be able to use the Resource.
App-V 5: Collect icons for XenApp 6 publishing.
Quick script you can run on a machine that will look at any app-v app cached on that machine and collect the icon file for it and spit it out into a structured directory.
Click here for the script.
Connect Network Printer via WMI
Very simple example, this can be run from any powershell ci:
([wmiclass]"Win32_Printer").AddPrinterConnection("\\\ ")
ex: <server> = printsrv01, <port> = R1414
The specific path should be fairly obvious if you browse to the printer server hosting the printer in question, it should show up as a device and you can just concatenate that onto the end and pipe it to WMI, simple.
PowerShell: Get First Open Drive Letter
Three lines of code to return the first in an array of available drive letters in the correct format for piping to New-PSDrive.
$letters = 65..90 | ForEach-Object{ [char]$_ + ":" } $taken = Get-WmiObject Win32_LogicalDisk | select -expand DeviceID $avail = ((Compare-Object -ReferenceObject $letters -DifferenceObject $taken)[1].InputObject).Replace(":","")
PowerShell: Random Password Generator.
Quick little script to generate a password meeting simple complexity (guaranteed one upper and one number at random positions in the string) guidelines.
$upper = 65..90 | ForEach-Object{ [char]$_ } $lower = 97..122 | ForEach-Object{ [char]$_ } $number = 48..57 | ForEach-Object{ [char]$_ } $total += $upper $total += $number $total += $lower $rand = "" $i = 0 while($i -lt 10){ $rand += $total | Get-Random; $i++ } "Initial: "+$rand $rand = $rand.ToCharArray() $num = 1..$rand.Count | Get-Random $up = $num while($up -eq $num){ $up = 1..$rand.Count | Get-Random } "num "+$num "up "+$up $rand[($num-1)] = $number | Get-Random $rand[($up-1)] = $upper | Get-Random $rand = $rand -join "" "Final: "+$rand
PowerShell: Get Remote Desktop Services User Profile Path from AD.
Bit goofy this, when trying to get profile path information for a user you may think Get-ADUser will provide you anything you would likely see in the dialog in AD Users and Computers, but you would be wrong. Get-ADUser -Identity <username> -Properties * does not yield a 'terminalservicesprofilepath' attribute.
Instead you must do the following:
([ADSI]"LDAP://").terminalservicesprofilepath
You can use Get-ADUser to retrieve the DN for a user or any other method you prefer.
Citrix: Presentation Server 4.5, list applications and groups.
Quick script to connect to a 4.5 farm and pull a list of applications and associate them to the groups that control access to them. You will need to do a few things before this works:
If you are running this remotely you need to be in the "Distributed COM Users" group (Server 2k3) and will need to setup DCOM for impersonation (you can do this by running "Component Services" drilling down to the "local computer", right click and choose properties, clicking General properties and the third option should be set to Impersonate).
Finally you will need View rights to the Farm. If you are doing this remotely there is a VERY strong chance of failure is the account you are LOGGED IN AS is not a "View Only" or higher admin in Citrix. RunAs seems to be incredibly hit or miss, mostly miss.
$start = Get-Date $farm = New-Object -ComObject MetaFrameCOM.MetaFrameFarm $farm.Initialize(1) $apps = New-Object 'object[,]' $farm.Applications.Count,2 $row = 0 [regex]$reg = "(?[^/]*)$" foreach($i in $farm.Applications) { $i.LoadData($true) [string]$groups = "" $clean = $reg.Match($i.DistinguishedName).Captures.Value $apps[$row,0] = $clean foreach($j in $i.Groups) { if($groups.Length -lt 1){ $groups = $j.GroupName }else{ $groups = $groups+","+$j.GroupName } } $apps[$row,1] = $groups $row++ } $excel = New-Object -ComObject Excel.Application $excel.Visible = $true $excel.DisplayAlerts = $false $workbook = $excel.Workbooks.Add() $sheet = $workbook.Worksheets.Item(1) $sheet.Name = "Dashboard" $range = $sheet.Range("A1",("B"+$farm.Applications.Count)) $range.Value2 = $apps $(New-TimeSpan -Start $start -End (Get-Date)).TotalMinutes
App-V 5.0: PowerShell VE launcher.
Quick little script to enable you to launch local apps into a VE. Can be run of two ways:
Prompts the user for an App-V app and then the local executable to launch into the VE.
Accepts command line arguments to launch the specified exe into the specified VE.
Import-Module AppvClient if($args.Count -ne 2) { $action = Read-Host "App-V app to launch into (type 'list' for a list of apps):" while($action -eq "list") { $apps = Get-AppvClientPackage foreach($i in $apps){ $i.Name } $action = Read-Host "App-V app to launch into (type 'list' for a list of apps):" } try { $apps = Get-AppvClientPackage $action } catch { Write-Host ("Failed to get App-V package with the following error: "+$_) } $strCmd = Read-Host "Local app to launch into VE:" try { Start-AppvVirtualProcess -AppvClientObject $app -FilePath $strCmd } catch { Write-Host ("Failed to launch VE with following error: "+$_) } }else{ $app = Get-AppvClientPackage $args[0] Start-AppvVirtualProcess -AppvClientObject $app -FilePath $args[1] }
Usage:
- Prompt-mode: AppV-Launcher.ps1
- CMDLine Mode: AppV-Launcher.ps1 TortoiseHg C:\Windows\Notepad.exe
Note: The arguments are positional, so it must be Virtual App then Local Executable in that order otherwise it will fail. There is no try/catch on the CMDLine mode as it expects you to know what you are doing (and want as much information about what went wrong as possible) and there is no risk of damage.
PowerShell: Date Picker.
Quick function to prompt a user to select a date. Usage is pretty straighforward.
$var = $(DatePicker "<title>").ToShortDateString()
function DatePicker($title) { [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") $global:date = $null $form = New-Object Windows.Forms.Form $form.Size = New-Object Drawing.Size(233,190) $form.StartPosition = "CenterScreen" $form.KeyPreview = $true $form.FormBorderStyle = "FixedSingle" $form.Text = $title $calendar = New-Object System.Windows.Forms.MonthCalendar $calendar.ShowTodayCircle = $false $calendar.MaxSelectionCount = 1 $form.Controls.Add($calendar) $form.TopMost = $true $form.add_KeyDown({ if($_.KeyCode -eq "Escape") { $global:date = $false $form.Close() } }) $calendar.add_DateSelected({ $global:date = $calendar.SelectionStart $form.Close() }) [void]$form.add_Shown($form.Activate()) [void]$form.ShowDialog() return $global:date } Write-Host (DatePicker "Start Date")
Citrix: Creating Reports.
A bit of a different gear here, but here are a couple examples, one using Citrix 4.5 (Resource Manager) andone using Citrix 6.0 (EdgeSight).
$start = Get-Date Import-Module ActiveDirectory function SQL-Connect($server, $port, $db, $userName, $passWord, $query) { $conn = New-Object System.Data.SqlClient.SqlConnection $ctimeout = 30 $qtimeout = 120 $constring = "Server={0},{5};Database={1};Integrated Security=False;User ID={2};Password={3};Connect Timeout={4}" -f $server,$db,$userName,$passWord,$ctimeout,$port $conn.ConnectionString = $constring $conn.Open() $cmd = New-Object System.Data.SqlClient.SqlCommand($query, $conn) $cmd.CommandTimeout = $qtimeout $ds = New-Object System.Data.DataSet $da = New-Object System.Data.SqlClient.SqlDataAdapter($cmd) $da.fill($ds) $conn.Close() return $ds } function Graph-Iterate($arList,$varRow,$varCol,$strPass) { Write-Host $arList[$i].depName foreach($i in $arList.Keys) { if($arList[$i].duration -ne 0) { if($arList[$i].depName.Length -gt 1) { $varRow-- if($arList[$i].depName -eq $null){ $arList[$i].depName = "UNKNOWN" } $sheet.Cells.Item($varRow,$varCol) = $arList[$i].depName $varRow++ $sheet.Cells.Item($varRow,$varCol) = ("{0:N1}" -f $arList[$i].duration) $varCol++ if($master -ne $true){ Iterate $arList[$i] $strPass } } } } return $varcol } function Iterate($arSub, $strCom) { $indSheet = $workbook.Worksheets.Add() $sheetName = ("{0}-{1}" -f $strCom,$arSub.depName) Write-Host $sheetName $nVar = 1 if($sheetName -eq "CSI-OPP MAX") { Write-Host "The Var is:" Write-Host $nVar $sheetName = "{0} {1}" -f $sheetName,$nVar $nVar++ } $strip = [System.Text.RegularExpressions.Regex]::Replace($sheetName,"[^1-9a-zA-Z_-]"," "); if($strip.Length -gt 31) { $ln = 31 }else{ $ln = $strip.Length } $indSheet.Name = $strip.Substring(0, $ln) $count = $arSub.Keys.Count $array = New-Object 'object[,]' $count,2 $arRow = 0 foreach($y in $arSub.Keys) { if($y -ne "depName" -and $y -ne "duration" -and $y.Length -gt 1) { $t = 0 $array[$arRow,$t] = $y $t++ $array[$arRow,$t] = $arSub[$y] $arRow++ } } $rng = $indSheet.Range("A1",("B"+$count)) $rng.Value2 = $array } function Create-Graph($lSheet,$lTop,$lLeft,$range, $number, $master, $catRange) { # Add graph to Dashboard and configure. $chart = $lSheet.Shapes.AddChart().Chart $chartNum = ("Chart {0}" -f $cvar3) $sheet.Shapes.Item($chartNum).Placement = 3 $sheet.Shapes.Item($chartNum).Top = $top $sheet.Shapes.Item($chartNum).Left = $left if($master -eq $true) { $sheet.Shapes.Item($chartNum).Height = 500 $sheet.Shapes.Item($chartNum).Width = 1220 }else{ $sheet.Shapes.Item($chartNum).Height = 325 $sheet.Shapes.Item($chartNum).Width = 400 } $chart.ChartType = 69 $chart.SetSourceData($range) $chart.SeriesCollection(1).XValues = $catRange } $port = "<port>" $server = "<sqlserver>" $db = "<db>" $user = "<db_user>" $password = "<pass>" $query = "SELECT p.prid, p.account_name, p.domain_name, p.dtfirst, cs.instid, cs.sessid, cs.login_elapsed, cs.dtlast, cs.session_type, s.logon_time, s.logoff_time FROM dbo.principal AS p INNER JOIN dbo.session AS s ON s.prid = p.prid INNER JOIN dbo.ctrx_session AS cs ON cs.sessid = s.sessid" #WHERE p.account_name LIKE 'a[_]%' $userlist = SQL-Connect $server $port $db $user $password $query $users = @{} foreach($i in $userlist.Tables) { if($i.account_name -notlike "h_*" -and $i.account_name -notlike "a_*" -and $i.account_name -ne "UNKNOWN" -and ([string]$i.logon_time).Length -gt 1 -and ([string]$i.logoff_time).Length -gt 1) { try { $info = Get-ADUser -Identity $i.account_name -Properties DepartmentNumber, Department, Company } catch { $info = @{"Company"="Terminated";"Department"="Invalid";"DepartmentNumber"="0000"} } if($info.Company.Length -lt 2) { $info = @{"Company"="Terminated";"Department"="Invalid";"DepartmentNumber"="0000"} } if($users.Contains($info.Company) -eq $false) { $users[$info.Company] = @{} $users[$info.Company]['duration'] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours }else{ $users[$info.Company]['duration'] = $users[$info.Company]['duration']+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours } if($users[$info.Company].Contains(([string]$info.DepartmentNumber)) -eq $false) { $users[$info.Company][([string]$info.DepartmentNumber)] = @{} $users[$info.Company][([string]$info.DepartmentNumber)]['depName'] = $info.Department $users[$info.Company][([string]$info.DepartmentNumber)]['duration'] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours }else{ $users[$info.Company][([string]$info.DepartmentNumber)]['duration'] = $users[$info.Company][([string]$info.DepartmentNumber)]['duration']+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours } if($users[$info.Company][([string]$info.DepartmentNumber)].Contains($i.account_name) -eq $false) { $users[$info.Company][([string]$info.DepartmentNumber)][$i.account_name] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours }else{ $users[$info.Company][([string]$info.DepartmentNumber)][$i.account_name] = $users[$info.Company][([string]$info.DepartmentNumber)][$i.account_name]+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours } }elseif($i.account_name -ne "UNKNOWN" -and ([string]$i.logon_time).Length -gt 1 -and ([string]$i.logoff_time).Length -gt 1) { if($i.account_name -like "a_*") { $info = @{"Company"="Administrators";"Department"="Elevated IDs (A)";"DepartmentNumber"="1111"} }else{ $info = @{"Company"="Administrators";"Department"="Elevated IDs (H)";"DepartmentNumber"="2222"} } if($users.Contains("Administrators") -eq $false) { $users['Administrators'] = @{} $users['Administrators']['duration'] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours }else{ $users['Administrators']['duration'] = $users['Administrators']['duration']+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours } if($users['Administrators'].Contains($info.DepartmentNumber) -eq $false) { $users['Administrators'][$info.DepartmentNumber] = @{} $users['Administrators'][$info.DepartmentNumber]['depName'] = $info.Department $users['Administrators'][$info.DepartmentNumber]['duration'] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours }else{ $users['Administrators'][$info.DepartmentNumber]['duration'] = $users['Administrators'][$info.DepartmentNumber]['duration']+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours } if($users['Administrators'][$info.DepartmentNumber].Contains($i.account_name) -eq $false) { $users['Administrators'][$info.DepartmentNumber][$i.account_name] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours }else{ $users['Administrators'][$info.DepartmentNumber][$i.account_name] = $users['Administrators'][$info.DepartmentNumber][$i.account_name]+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours } }else{ if(([string]$i.logon_time).Length -lt 1 -and $i.account_name -ne "UNKNOWN"){ "No logon time: "+$i.account_name } if(([string]$i.logoff_time).Length -lt 1 -and $i.account_name -ne "UNKNOWN"){ "No logoff time: "+$i.account_name } } } # Create Excel object, setup spreadsheet, name main page. $excel = New-Object -ComObject excel.application $excel.Visible = $true $excel.DisplayAlerts = $false $workbook = $excel.Workbooks.Add() $row = 1 $col = 1 $sheet = $workbook.Worksheets.Item(1) $sheet.Name = "Dashboard" # Populate tracking vars. # $row is the starting row to begin entering data into text cells. # $cvar tracks $left position, resets when it reaches 3. # $cvar3 tracks $top position, after every third graph it increments +340. $row = 202 $col = 2 $cvar = 1 $cvar3 = 1 $top = 10 $left = 10 # Iterate through main element (Companies), $z returns company name (MGTS, MR, etc.). $min = ($sheet.Cells.Item(($row)-1,1).Address()).Replace("$", "") $tmin = ($sheet.Cells.Item(($row)-1,2).Address()).Replace("$", "") foreach($q in $users.Keys) { $sheet.Cells.Item($row,1) = "Maritz Total Citrix Usage (by hours)" $row-- if($q -eq "114"){ $q = "Training IDs" } $sheet.Cells.Item($row,$col) = $q $row++ $sheet.Cells.Item($row,$col) = ("{0:N1}" -f $users[$q].duration) $col++ } $max = ($sheet.Cells.Item($row,($col)-1).Address()).Replace("$", "") $range = $sheet.Range($min,$max) $range2 = $sheet.Range($tmin,$max) Create-Graph $sheet $top $left $range $cvar3 $true $range2 $row++;$row++ $col = 2 $top = ($top)+510 $cvar3++ foreach($z in $users.Keys) { if($z.Length -gt 1 -and $z -ne "112 MAS"){ # Setup chart location vars. if($cvar -eq 1) { $left = 10 }elseif($cvar -eq 2){ $left = 420 }elseif($cvar -eq 3) { $left = 830 } $col = 2 $sheet.Cells.Item($row,1) = $z # Track chart range minimum cell address. $min = ($sheet.Cells.Item(($row)-1,1).Address()).Replace("$", "") $tmin = ($sheet.Cells.Item(($row)-1,2).Address()).Replace("$", "") # Iterate through secondary element (Departments), $i returns department name. # Graph-Iterate Here $vLoc = Graph-Iterate $users[$z] $row $col $z # Track chart range maximum cell address. $max = ($sheet.Cells.Item($row,($vLoc)-1).Address()).Replace("$", "") $range = $sheet.Range($min,$max) $range2 = $sheet.Range($tmin,$max) Create-Graph $sheet $top $left $range $cvar3 $false $range2 $row++;$row++ # Increment or reset tracking vars. if($cvar -eq 3) { $top = ($top)+340 } if($cvar -eq 1 -or $cvar -eq 2){ $cvar++ }elseif($cvar -eq 3){ $cvar = 1} $cvar3++ } } # Show dashboard page rather than some random department. $sheet.Activate() New-TimeSpan -Start $start -End (Get-Date)
XenServer: Activate SDK in 64-bit PowerShell.
Not exactly new info, just putting this here so I can copy and paste instead of remembering it. :)
C:\Windows\Microsoft.NET\Framework64\<version>\InstallUtil.exe "c:\Program Files (x86)\Citrix\XenServerPSSnapIn\XenServerPSSnapIn.dll"
App-V: ADM-Get-Assoc
A script to connect to the App-V DB and find what all software is assigned to a particular user or group.
All the instructions you should need are in the script itself.
PowerShell: I'm Going To Replace You With A Script.
We've all said it, some of us have gone to great lengths to actually DO it.
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") $screen = [System.Windows.Forms.SystemInformation]::VirtualScreen [int]$target = 60 [int]$i = 1 while($i -lt $target){ $randx = Get-Random -Minimum 1 -Maximum $screen.Width $randy = Get-Random -Minimum 1 -Maximum $screen.Height [Windows.Forms.Cursor]::Position = "$($randx), $($randy)" $i++ Start-Sleep -Seconds 10 }
Initial testing indicates this would replace approx. 80% of corporate IT. Welcome your new overlords.
Well, I supposed you'd need to up the duration...
PowerShell: XenServer Get VM IP Address
This like all of these PowerShell XenServer examples requires you load the snapin and an active connection to a XenServer.
First you need to get a reference to your VM of choice, in this case I know the exact VM by name so I do this:
$vms = Get-XenServer:VM | Where-Object {$_.name_label -eq "<nameofVM>"}
Now I want to read the guest metrics on this VM to find the IP Address, note that this requires XSTools to be installed and the IP address wont be available right away, typically it seems to only be available after the machine has both booted, and had time for the XSTools to report it's IP to guest metrics, but once it's available you can get it by doing this:
Get-XenServer:VM_guest_metrics.Networks -VMGuestMetrics $vms.guest_metrics
Be aware that if you want to get a list of IP's for more than one VM you will need to foreach through $vms and run the command for each one.
One potential use of this is to, say, clone a new VM from a template, start it, wait for it to establish a network connection then Test-Connection until you get a result, at which time you can proceed to do whatever else you need to do (via WinRM for instance, if you have it enabled in the template).
PowerShell: XenServer Messages (Since)
If you are expecting a message to occur after an action you are taking the following may be of use to you.
$messages = Get-XenServer:Message.Since -Since [System.DateTime]::Now foreach($i in $messages.Values) { $i }
Obviously this doesn't do anything intelligent like look for the specific event, but once you see the output you can customize it to do whatever you want.
PowerShell: XenServer Recreate ISO Store
A little too much code to past directly on SquareSpace (haven't had time to figure out a way around that) but here it is.
As I've had a couple questions I figured I'd post this. Once you install the snapin you need to run the following command (most likely with admin rights), why they don't do this as part of the install I don't know, I could see there being some "security" reason.
PS C:\Windows\Microsoft.NET\Framework64\v4.0.30319> .\InstallUtil.exe 'C:\Program Files (x86)\Citrix\XenServerPSSnapIn\XenServerPSSnapIn.dll'
PowerShell: Destroy CIFS ISO Library
This is step one of two in destroying (and then re-creating) a CIFS ISO library in XenServer, the reasons you may need to do this are varied, this is personally useful to me in a lab environment when the repo location or user credentials may change often. Everythign below requires the XenServer PSSnapin.
First thing we want to do is snag the info for the SR we want to delete:
$sr = Get-XenServer:SR | Where-Object {$_.content_type -eq "iso" -and $_.type -ne "udev" -and $_.name_label -ne "XenServer Tools"}
This filters out the CD-ROM (udev) and XS Tools and just returns us SR's whose content type is ISO, so it shouldn't matter what you name your repo (um, as long as you don't name it XenServer Tools I guess).
Now we need to unplug it:
foreach($i in $sr.PBDs){Invoke-XenServer:PBD.Unplug -SR $i}
Do you need to foreach this? Probably not. But if you DO happen to set it up with multiple PBD's then it will still fail on the next step because you only unplugged some of the PBD's, when in doubt, be thorough.
Now lets remove the SR:
Invoke-XenServer:SR.Forget -SR $sr.name_label
There you go, the next step will be to ask for a path, user and password to create a new ISO Library, which I'll cover next time.
App-V 5.0: Package Conversion Script
A quick PowerShell script with logging to convert a directory full of App-V packages.
$src = "<source path>\" $dst = "<destination path>" $logdir = "<logfile location>\ConversionLog.txt" Import-Module AppvPkgConverter $list = dir $src|where {$_.mode -match "d"} If((Test-Path $logdir) -eq $false) { New-Item($logdir) -Type File } foreach($i in $list) { Write-Host $src$i $conv = ConvertFrom-AppvLegacyPackage -SourcePath $src$i -DestinationPath $dst If($conv.Error -ne $null -or $conv.Warnings -ne $null) { Add-Content -Path $logdir -Value ($conv.Source+" appears to have failed...`n") Add-Content -Path $logdir -Value ("Error: "+$conv.Errors+"`nWarning: "+$conv.Warnings+"`n") }elseif($conv.Information -ne $null){ Add-Content $logdir $conv.Information"`n" }else{ Add-Content -Path $logdir -Value ($conv.Source + " completed ok, no Errors or Warnings...`n") } }
DataNow: Install Tips
Couple of quick things to think about if you are installing DataNow.
- The default initial admin username is "appliance".
- It doesn't support chrome (which you will find out if you try to import a certificate) during config.
XenServer Appliance Import Fails
If you find your import sitting at "Connecting..." with a reference to the specific VDI it is trying to create at the end, 99% chance it failed to get an IP. At the last phase of the import just specify a static IP, in my case for whatever reason DHCP wasn't responding fast enough so even though it LOOKED like it was getting an IP it just wasn't making the connection. Manually setting an IP solved it.