PowerShell: XenServer 6.0 CmdLet Poster

Fire up your large format printers!

The fine folks at X-Tech have put together an exhaustive (and large) poster showing all the existing and new cmdlets for managing XenServer through PowerShell.

An Example of the  XenServer cmdlet poster
An Example of the XenServer cmdlet poster

Get your copy here.

Thanks,
Alain

XenServer: Creating an ISO Partition on DOM0

Intro

I recently updated my lab machine to XenServer 6.0 and I wanted to create a local ISO repository on the DOM0 partition. I have 3 physical drives, one 250GB drive that holds the host partition and its backup and two 500GB drives that host VM’s. I know that only 8GB on the 250 GB drive are used for the host and its backup, so I wanted to create the local ISO repository in the remaining space.

Stop! Linux Time

Connect to the CLI of your XenServer.

fdisk –l shows my current partition tables

[root@MARLINSPIKE ~]# fdisk -l

WARNING: GPT (GUID Partition Table) detected on '/dev/sda'! The util fdisk doesn't support GPT. Use GNU Parted.

Disk /dev/sda: 500.1 GB, 500107862016 bytes
255 heads, 63 sectors/track, 60801 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

Disk /dev/sda doesn't contain a valid partition table

WARNING: GPT (GUID Partition Table) detected on '/dev/sdb'! The util fdisk doesn't support GPT. Use GNU Parted.

Disk /dev/sdb: 500.1 GB, 500107862016 bytes
255 heads, 63 sectors/track, 60801 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

Disk /dev/sdb doesn't contain a valid partition table

WARNING: GPT (GUID Partition Table) detected on '/dev/sdc'! The util fdisk doesn't support GPT. Use GNU Parted.

Disk /dev/sdc: 250.0 GB, 250059350016 bytes
256 heads, 63 sectors/track, 30282 cylinders
Units = cylinders of 16128 * 512 = 8257536 bytes

Device Boot      Start         End      Blocks   Id  System
/dev/sdc1   *           1       30283   244198583+  ee  EFI GPT

Dom0 contains 3 partitions. The first is where the XenServer host resides. The second is the host backup. The final partition is the rest of the unused space on the 250GB drive. In my file system, this is /dev/sdc3. The following commands will format and mount this space as an ISO partition.


[root@MARLINSPIKE ~]# mkfs.ext3 /dev/sdc3
mke2fs 1.39 (29-May-2006)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
29491200 inodes, 58952233 blocks
2947611 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=0
1800 block groups
32768 blocks per group, 32768 fragments per group
16384 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624, 11239424, 20480000, 23887872

Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done
This filesystem will be automatically checked every 32 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
[root@MARLINSPIKE]# mkdir /mnt/iso/
[root@MARLINSPIKE]# mount -t ext3 /dev/sdc3 /mnt/iso/
[root@MARLINSPIKE]# echo "/dev/sdc3 /mnt/iso ext3 defaults 1 1" >> /etc/fstab
[root@MARLINSPIKE]# xe-mount-iso-sr /mnt/iso -o bind

Now the new ISO partition shows up in my XenCenter console.

image

Sources:

Citrix Forums: Thread: Dom0 Partitions

Citrix Forums: Thread: xe sr-create, local ISO SR on larger drive

How to add an additional local disk to your XenServer 5.5 host

XenServer create local ISO Repository (LVM)

How To Re-partition a Xen Virtual Machine Using GParted LiveCD

LinuxQuestions.org: Having problems mounting hd. (mount: you must specify the filesystem type)

Thanks,
Alain

PowerShell: Message of the Day

Intro

In an effort to be more communicative with our users on changes made to our XenApp servers we decided to create a message of the day.  Here are the criteria we decided on:

  1. The message would show as part of the user’s startup script when logging into XenApp/XenDesktop
  2. The user would have to click a button to remove the message box and the result could be acted on by the script. In one example, if the user pressed the Yes button, then we could open a page to our intranet.
  3. A hidden flag file would be written to a user’s home directory to note that the user had already seen the message.
  4. The Message file itself should be easily edited by any member of the team and not require any scripting knowledge.

I wore Google out looking for various solutions and examples to get the script working the way I wanted. I do not consider myself an expert in PowerShell by any means, but hopefully this post will give you some ideas to try in your environment.

Assumptions

Our environment supports development, staging and production environments along with different XenApp images depending on software being delivered. As a result, the scripts and the text file that is the message of the day are sync’d to the C: drive of the provisioned image. This allows us to tailor messages to different user populations.

While running, the script will archive the existing message, and since the user does not have permission to write to the C: drive (this script runs during a user’s login as the user) I’m using an admin account to write the archive file to the C: drive.

The Script

I’m using Show-Messagebox.ps1 to create the message box provided by Daniel Sörlöv (original link, his new site):

This script is located in the LocalCache folder along with motd.ps1 on the XenApp server.


param ($title,$text,$buttons="OK",$icon="None")

$FormsAssembly = [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$dialogButtons = @{
 "OK"=[Windows.Forms.MessageBoxButtons]::OK;
 "OKCancel"=[Windows.Forms.MessageBoxButtons]::OKCancel;
 "AbortRetryIgnore"=[Windows.Forms.MessageBoxButtons]::AbortRetryIgnore;
 "YesNoCancel"=[Windows.Forms.MessageBoxButtons]::YesNoCancel;
 "YesNo"=[Windows.Forms.MessageBoxButtons]::YesNo;
 "RetryCancel"=[Windows.Forms.MessageBoxButtons]::RetryCancel }

$dialogIcons = @{
 "None"=[Windows.Forms.MessageBoxIcon]::None
 "Hand"=[Windows.Forms.MessageBoxIcon]::Hand
 "Question"=[Windows.Forms.MessageBoxIcon]::Question
 "Exclamation"=[Windows.Forms.MessageBoxIcon]::Exclamation
 "Asterisk"=[Windows.Forms.MessageBoxIcon]::Asterisk
 "Stop"=[Windows.Forms.MessageBoxIcon]::Stop
 "Error"=[Windows.Forms.MessageBoxIcon]::Error
 "Warning"=[Windows.Forms.MessageBoxIcon]::Warning
 "Information"=[Windows.Forms.MessageBoxIcon]::Information
}

$dialogResponses = @{
 [System.Windows.Forms.DialogResult]::None="None";
 [System.Windows.Forms.DialogResult]::OK="Ok";
 [System.Windows.Forms.DialogResult]::Cancel="Cancel";
 [System.Windows.Forms.DialogResult]::Abort="Abort";
 [System.Windows.Forms.DialogResult]::Retry="Retry";
 [System.Windows.Forms.DialogResult]::Ignore="Ignore";
 [System.Windows.Forms.DialogResult]::Yes="Yes";
 [System.Windows.Forms.DialogResult]::No="No"
}

return $dialogResponses[[Windows.Forms.MessageBox]::Show($text,$title,$dialogButtons[$buttons],$dialogIcons[$icon])]

The Get-Checksum function is from Moe Winnett at the TechMumboJumblog. I use this function to generate a checksum of the current message.txt file that will be displayed to the user. Once the message is displayed, a hidden .flag file is written to the user’s home directory containing the message.txt checksum. When the script checks to see if the user has seen the message, a checksum is generated of the current message.txt and compared to the checksum written to the .flag file. If they match, the script assumes that the user has already seen the message and will not display it.


######################################################################
### Get-Checksum function #############################################

function Get-Checksum($file, $crypto_provider) {
 if ($crypto_provider -eq $null) {
 $crypto_provider = new-object 'System.Security.Cryptography.MD5CryptoServiceProvider';
 }

$file_info = get-item $file;
 trap { ;
 continue } $stream = $file_info.OpenRead();
 if ($? -eq $false) {
 return $null;
 }

$bytes = $crypto_provider.ComputeHash($stream);
 $checksum = '';
 foreach ($byte in $bytes) {
 $checksum += $byte.ToString('x2');
 }

$stream.close() | out-null;

return $checksum;
}
#######################################################################
### Main Code #########################################################

##Set env variables
$hostname = gc env:computername
$lc = (get-item env:localcache).value
$today=get-date
$usrname = [Environment]::UserName
$usrprofile = (get-item env:userprofile).value
$usrPath = "$usrprofile\$usrname"

##Set credentials
$username="DOMAIN\CITRIXADMIN"
$Passwd = "PASSWORD1"

## Set file paths
$MsgBox = "$lc\scripts\Show-MessageBox.ps1"
$MsgPath ="$lc\message"
$MsgFile = "message.txt"
$MsgFilePath = $MsgPath + '\' + $MsgFile
$userhome = (get-item env:USERHOME).Value
$debug = $true

##Check if there is a message file and get its checksum, if not exit the script
if (!(test-path $MsgFilePath)) {
 if ($debug) {write-host "No MOTD file"}
 exit
} else {
##Check whether Message has been archived
 if (Get-Content -LiteralPath $MsgFilePath | Select-String -Pattern "ARCHIVE" -Quiet)
 {
 if ($debug) {write-host "MOTD file was already archived"}
 } else {
 if (!(test-path $MsgPath\archive)) {
 md $MsgPath\archive | out-null
 if ($debug) {write-host "Created archive folder"}
 }
 $ArchiveNote="`r`nARCHIVE - Delete this line if editing file"
 net use \\$Hostname\c$ /user:$username $Passwd
 $tmppath="\\$hostname\c$\localcache\message\message.txt"
 add-content $tmppath $ArchiveNote
 net use \\$Hostname\c$ /delete
 copy $MsgPath\$MsgFile "$MsgPath\archive\MOTD_$((get-date).toString('yyyyMMdd')).txt"
 if ($debug) {write-host "Archived $MsgFilePath to $MsgPath\archive\MOTD_$((get-date).toString('yyyyMMdd')).txt"}
 }
 $MsgChecksum = get-checksum($MsgFilePath)
}

##Check if the user has already seen the message
if (test-path $flexpath\.flag) {
 if ($debug) {write-host "User has seen a message - check if it's current message"}
 $a = get-item $flexpath\.flag -Force
 $flgchecksum = Get-Content $a
 if ($flgchecksum -eq $MsgChecksum) {
 if ($debug) {write-host "User has seen today's message - exit"
 exit
 }
 } else {
 del $a -force
 $flagfile = new-item $flexpath\.flag -type file
 Add-Content $flagfile $MsgChecksum
 Set-ItemProperty -Path $flagfile -Name Attributes -Value ([System.IO.FileAttributes]::Hidden)
 if ($debug) {write-host "User's old .flag file deleted"}
 if ($debug) {write-host "Created hidden flag file: $flagfile and wrote checksum: $MsgChecksum"}
 }
} else {
 $flagfile = new-item $flexpath\.flag -type file
 Add-Content $flagfile $MsgChecksum
 Set-ItemProperty -Path $flagfile -Name Attributes -Value ([System.IO.FileAttributes]::Hidden)
 if ($debug) {write-host "User has not seen the message"}
 if ($debug) {write-host "Created hidden flag file: $flagfile and wrote checksum: $MsgChecksum"}
}

## Create Window Title with date
$dt = get-date -format D
$tit = "Message of the Day for " + $dt

## Display MessageBox
$Msg = [string]::join([environment]::newline, (get-content -LiteralPath $MsgFilePath))
$MsgB = $Msg.Replace("ARCHIVE - Delete this line if editing file","")

$result = & $MsgBox $tit $MsgB "YesNo" "Information"

## Act on result
if ($result -eq "No") {
 Write-Host "User Pressed: $result"
}

if ($result -eq "Yes") {
 Write-Host "User Pressed: $result"
}

Message.txt

Message.txt is the file that is read by the MOTD.ps1 script and displayed to the user.  If you edit the file and see the archive message in the file, you must delete the archive message…

This will ensure the new message is archived by the script.  Currently the script assumes there will be only one message a day, so if a change is made to the script it will overwrite that day’s archived message.  The message.txt is stored in the localcache folder on the server in the Message folder.  The script will create an Archive folder under the Message folder if none is present.

Script Logic Flow

 

Thanks,
Alain