Working with the "back-end" of I.T. systems. Learn. Apply. Repeat.
The things learned as an IT Pro'fessional turned software tester, researcher, and product manager.
Wednesday, May 21, 2014
SCVMM is deploying using the wrong VHD or VHDX
This is a classic case of 'as designed' being exposed through changes in behavior as an implementation evolves. Combined with what you see in the UI not really being what happens in the system.
I like it because I consider it a bug due to the fact that the behavior has changed between release and applying Update Rollups.
The symptom is this: you deploy a VM (or a bare metal deployment) and you observe that SCVMM is using the incorrect virtual disk.
And you noticed this happening consistently after applying UR2 (IMHO - that is the key differentiator in the behavior change).
In a nutshell - SCVMM is selecting the virtual disk from the Library. And it is actually getting more than one from the Library and it is using the incorrect one.
Lets back up a bit. How is SCVMM ending up with an incorrect virtual disk from the Library in the first place.
Objects in the SCVMM Library have this concept of equivalency. Multiple objects being equivalent to each other, though they are different objects.
And virtual disks are one of these objects.
Lets say that you create one VHD and this is Server 2012 R2, sysprep'd, all ready to go. You assign that to a Host Profile or a Service Template, or a WAP Gallery Item. You set the version to 1.0.0.0 and the family to 'Server 2012 R2' and the OS to 'Server 2012 R2 Datacenter'.
You perform some test deployments and all is good, you move ahead and use it.
After a bit, you get new hardware and you create a new VHD. Identical to the first except you add some additional drivers for the new storage cards and NICs.
You set the version and family and OS the same.
You then open the Host Profile or Service template and select this new disk.
You deploy, and ... your hardware does not work. Your new device drivers are missing. You jump up and down and bang your head on the wall.
You run around in circles.
What happened was this. SCVMM didn't select the single object that you think you selected. It selected the OS, family, and version. And it got two back from the Library. It then used the "first" one, not the second one. (this is the behavior change - prior to UR2 it was as if the one with the later date was used, but no longer).
Make the version unique (or the family name) and you will fix the problem.
According to the VMM team - this is 'as designed'. That is developer speak for: We built it to work that way.
I hope the documentation will soon describe this equivalency concept. Since it currently mentions it in passing and never really describes anything about it.
Until then, I hope search brings you here.
Monday, November 4, 2013
Deploying XenDesktop using the SCVMM Service Template
The latest release of XenDesktop is now available as a Service Template for System Center Virtual Machine Manager.
I am assuming that my blog readers are already familiar with the concept of Service Templates, introduced with SCVMM 2012.
The “Service” is where the applications and OS are separate entities, they are layered across each other and deployed as a composed entity with intact references and dependencies. The Template is the representation of these dependencies and relationships. The Template is ‘deployed’ into a ‘Service’. The Service is the running machines.
For most of the past year we have been focused on simplifying the deployment of the XenDesktop infrastructure. After all, there are enough decisions to make without having to spend one or two days installing operating systems, applications, and configuring them. This is where the XenDesktop VMM Service Template comes in.
The whole idea is to take the monotonous tasks of deploying VMs, installing XenDesktop, configuring those infrastructure machines and reducing them to a few questions and time. Freeing you up to do more important things. Ant the end you can have a distributed installation of XenDesktop – the license server, Director, StoreFront, and Controller. All connected and ready to deliver applications or desktops.
Why not give it a go?
If you just want to see what this is all about, take a look here: http://www.citrix.com/tv/#videos/9611
If you head over to the XenDesktop download page, you will find a “Betas and TechPreviews” section. In there you can download the XenDesktop Service Template zip package. (The Service Template is the TechPreview not the version of XenDesktop).
By the way – there are four templates. One template to install a scaled out XenDesktop, another for an evaluation installation of XenDesktop. You will also find Provisioning Server templates that can also support scaling out or an evaluation installation.
After downloading the package, unzip it to a convenient location, then open up the SCVMM Console, Select the Library view, and click on the Import button in the ribbon.
You can always stop here and read the administration guide, it is short and has all the pretty screen shots that this post is missing).
Browse to the XML file in the package you just unzipped.
Then map your generalized Server 2012 (or Server 2012 R2) VHD / VHDX to the package by selecting the pencil (a red X appears when it is mapped – don’t ask me why a red X)
Just like the generalized virtual disk, if you want SCVMM integration enabled, then place the SCVMM installation ISO in your VMM Library and select that pencil icon to create the mapping.
The Custom Resource should be uploaded from the package and contains the Citrix parts.
There is a really short import video here if you don’t want to read all of that.
After you import, you can deploy the XenDesktop infrastructure by simply right clicking the template and selecting Configure Deployment. Answer a few pertinent questions, select Refresh Preview for SCVMM to place the machines, and select Deploy Service. The name you give your Service will also become the name of your XenDesktop Site.
Now, go to lunch. When you return, connect to the console of the Controller VM, open Studio, and begin publishing desktops.
You can watch a shortened version of the deployment process.
The requirements are not different than any version of XenDesktop. There needs to be; a domain to join with DNS, a SQL Server, a VM Network from which the machines can reach those resources, a RunAs account for the (first) XenDesktop administrator, and a user account for XenDesktop to integrate with SCVMM.
As time passes you may decide that you need additional StoreFront Servers or Desktop Controller servers. To do that, select your Service in the VMs and Services view, right click, and Scale Out. Select the tier and go. Additional Controller capacity is created for you and added to the Site, StoreFront requires some additional configuration so you can tailor the load balancing to your environment.
If you need to see that one, I have a video for that as well.
If you need support, you can get that in the XenDesktop forums, we will be there to help and respond to questions.
Please, give us feedback and let us know what you think.
Friday, October 25, 2013
SCVMM Service deployment and NO_PARAM server: NO_PARAM: NO_PARAM
I have to say. This particular error is my favorite of all time (so far).
Here is the scenario:
- I deploy a Service form a Template.
- I wait.
- The Job fails.
- I check the SCVMM Job log and see something resembling this:
Error (2912)
An internal error has occurred trying to contact the NO_PARAM server: NO_PARAM: NO_PARAM.
NO_PARAM
Recommended Action
Check that WS-Management service is installed and running on server NO_PARAM. For more information use the command "winrm helpmsg hresult". If NO_PARAM is a host/library/update server or a PXE server role then ensure that VMM agent is installed and running.
Error (20400)
1 parallel subtasks failed during execution.
Error (2912)
An internal error has occurred trying to contact the NO_PARAM server: NO_PARAM: NO_PARAM.
NO_PARAM
Recommended Action
Check that WS-Management service is installed and running on server NO_PARAM. For more information use the command "winrm helpmsg hresult". If NO_PARAM is a host/library/update server or a PXE server role then ensure that VMM agent is installed and running.
Error (20400)
1 parallel subtasks failed during execution.
I can tell you from experience that this error has absolutely nothing to do with WinRM. In fact, if you spend time there, it is wasted.
So, what happened?
In a nutshell; your script / installer ran, and it did not throw a single error. Not one. But, your timeout setting was too low due to something, anything and SCVMM gave up waiting for the Exit Code 0 that your script had finished.
Recall, that there was no error, so SCVMM did not have one to pass back up the chain to you and put in the job log. That is where all of this NO_PARAM business is coming from. Literally, no error was passed to something as a parameter and that particular piece of code is simply stating that it didn’t receive one.
And SCVMM reports this error as an error and pattern matches it and attempts to give you some guidance around it – where the WinRM part comes from.
I first caused this to happen because my script was stalled with a dialog box that was open, waiting for someone to respond, and since everything you define in your Service Template runs headless, there is no way to even know the dialog appeared – other than to logon to your VM and see that the script process continues to run.
I have also seen this happen again when there is high disk IO causing the various installers or configuration scripts to actually run slower.
Just to give a few clues as to why you see this in the first place, as it is a real mystery until you figure it out. It took me a couple weeks to sort it all out. Now, I avoid it – I spread my VMs across my hosts by selection.
Thursday, September 12, 2013
PowerShell to enable Remote Desktop for Administration on the local machine
I had a teammate request that I enable Remote Desktop for Administration as a portion of my SCVMM Service Template.
You cannot script sconfig – although that is a easy manual way to do it.
If you try any of the Server 2012 cmdlets you will end up mucking with Remote Desktop Services and enabling user access.
Well, it turns out the key is a key. And it is easiest to tweak it with WMI.
The following script runs on the server that is being modified (localhost is the default). And it can run using administrator or local system security credentials.
try {
$RDP = Get-WmiObject -Class Win32_TerminalServiceSetting `
-Namespace root\CIMV2\TerminalServices
# -Computer $Computer `
# -Authentication 6 `
# -ErrorAction Stop
} catch {
"WMIQueryFailed"
continue
}
if($RDP.AllowTSConnections -eq 1) {
"RDP Already Enabled"
continue
} else {
try {
$result = $RDP.SetAllowTsConnections(1,1)
if($result.ReturnValue -eq 0) { "Enabled RDP Successfully" }
if ($result.ReturnValue -eq 4096) {
$Job = [WMI]$Result.Job
while ($Job.JobState -eq 4) {
Write-Progress -Id 2 -ParentId 1 $Job.Caption -Status "Executing" -PercentComplete $Job.PercentComplete
Start-Sleep 1
$Job.PSBase.Get()
}
}
} catch {
"Failed to enable RDP"
}
}
Wednesday, September 4, 2013
PowerShell to disable IE Enhanced Security
So, my employer has a number of web consoles for various applications.
This is fine, except for pesky IE Enhanced Security.
So, to automatically disable this for members of the local Administrators group just comment out the User section from the script below.
Now, before you reply that I should be adding the URL to the exclusion list and all that. This is so much simpler. Why? Because I don’t have to worry about a shortcut having localhost vs. the FQDN in it.
This one section of my script runs and Administrators are happy. After all, these are servers. And outside of hitting a local console once or twice or applying updates, they should not even be logged in locally, right(?)
# Disable IE Enhanced Security Configuration for Administrators and Users for web consoles
try {
$AdminKey = “HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}”
$UserKey = “HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}”
Set-ItemProperty -Path $AdminKey -Name “IsInstalled” -Value 0
Set-ItemProperty -Path $UserKey -Name “IsInstalled” -Value 0
Stop-Process -Name Explorer
“IE Enhanced Security Configuration (ESC) has been disabled on this machine.”
}
catch {"Failed to disable IE ESC" }
Friday, August 30, 2013
Zip files and folders with PowerShell
One of the more frustrating things in this day of PowerShell v3 is not having cmdlets that can simply manipulate ZIP archives.
It is right there in the Windows GUI, but is it easy to automate? Nope.
In fact if you search around you will find lots of different ways to handle this, one getting more complex than the next. You will also find community projects that attempt to do the same thing.
One common reference that I ran across was this:
From the spectacular David Aiken. I have to admit, it is not the first time he has saved my bacon.
His solution is built in, no funky community add-ins, nothing strange I can’t follow, and best of all it is compact – it is really small.
I have already had to do some things with Shell.Application with PowerShell, so I figured I would give it a shot.
Well, I immediately ran into some issues.
One, his use of –Recurse. Not necessary. Use Get-ChildItem and pipe in the folder and then entire folder is zipped. Perfect. So you can work the input just about any way you like and it will simply pass whatever you pipe to it.
Two, file locking. This tripped my up for hours. And lots of other folks that have found his solution too. His little 500 millisecond wait before advancing on to the next is simply not real nor flexible based on varying file sizes.
I found all kinds of folks commenting on the same thing and developing all kinds of fancy solutions to handle it. But, in the end I found something really simple, and it was buried right there in the shell.application all along. Simply test for the existence of the item you are zipping in the zip archive.
So simple. One little do loop. With my crafty Until ( $zipPackage.Items() | select {$_.Name -eq $file.Name} )
And, I only modified the Add-Zip, since David already had a fail safe to create the .ZIP if it didn’t already exist.
I am going to update my use of his usage example as well.
In my case I have a number of XML files. Each is in a unique Folder. There are other files and folders with the XML files as well (this is an OVF, for you OVF fans).
I want to create the .ZIP one level up from the folder where the XML is.
$zipFullName = $ovfFolder.Parent.FullName + "\" + $xmlFile.BaseName + ".zip" Get-ChildItem $ovfFolder | Add-Zip $zipFullName $ovfFolder = Get-Item $xmlFile.PSParentPath
Now, below is my modification to the original Add-Zip function.
if(!(test-path($zipfilename)))function Add-Zip # usage: Get-ChildItem $folder | Add-Zip $zipFullName
{
param([string]$zipfilename)
{
set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
(dir $zipfilename).IsReadOnly = $false
}
$shellApplication = new-object -com shell.application
$zipPackage = $shellApplication.NameSpace($zipfilename)
foreach($file in $input)
{
$zipPackage.CopyHere($file.FullName)
do {
Start-sleep 2
} until ( $zipPackage.Items() | select {$_.Name -eq $file.Name} )
}
}
Monday, June 24, 2013
Passing and parsing @ServiceVMComputerNames@
In my past post I mentioned the undocumented Service Settings that SCVMM will automatically fill-in for you and pass to your Application Scripts.
But, how can we pass these?
Some are easy, you just pass them like any other setting since they are relatively short strings.
For example, in testing things for this article I passed @ServiceVMComputerNames@, @ComputerName@, @ServiceName@, @VMID@, and @ServiceID@. I had no idea how long these might be or what they might look like.
My Test Service had two tiers. One tier with two VMs, one tier with one VM. It looks like this:
When I deployed the Service I named it “blah”, I have a tier named “The Tier” and another named “The Other Tier”, and three VMs named “xVM01”, “xVM02”, and “yVM01” (SCVMM applied the numbers using the ## notation).
Within VM xVM01 I sent and captured all of the settings I have mentioned.
What I got out was:
- @ServiceVMComputerNames@ = “The Tier[xVM01,xVM02]The Other Tier[yVM01]”
- @ComputerName@ = “xVM01”
- @ServiceName@ = “blah”
- @VMID@ = “26fd4a55-a707-4fba-89b5-c6955e4e05a2”
- @ServiceID@ = “741fbf99-e676-4a8b-9df7-096c0be1fd3e”
These short service names you can safely pass using: myscript.ps1 –paramName @VMID@ or myscrpt.cmd @VMID@
There are lots of examples about that.
It is @ServiceVMComputerNames@ that could get really long and in turn make the command line too long to execute. So, this one I pass in a bit differently. To accommodate the length I pipe the setting to my PowerShell script (as I blogged about here).
In the Service Template designer this looks like:
My script receives the object as a pipeline and writes it out.
Within the script:
Param
(
[parameter(Mandatory = $false, ValueFromPipeline = $true)]
$serviceNames = ""
)
$logPath = "$env:ProgramData\testPath\"
# write out the string for debugging
$serviceNames | Out-File ($logPath + $startTime.Second + $startTime.Millisecond + ".txt")
Then I can just keep reusing this snip to see what I end up with.
What I in turn do with this input is I parse it into an XML document that I later re-use with other SCVMM Application Scripts.
Param
(
[parameter(Mandatory = $false, ValueFromPipeline = $true)]
$serviceNames = ""
)
$logPath = "$env:ProgramData\MyService\"
# make the path so Out-File will be happy
If (!(Test-Path -Path $logPath -PathType Container)){
New-Item -Path $logPath -ItemType Container -Force
Start-Sleep 10 # so the OS can register the path
}
# Parse the Service information from SCVMM
$tiers = $serviceNames.Split(']')
$service = New-Object System.Xml.XmlDocument
$root = $service.CreateElement("ServiceTemplate")
$root.SetAttribute("version","1.0")
$root.SetAttribute("createon",(Get-Date))
$root.SetAttribute("createdby","brianeh")
foreach($tierString in $tiers) {
if($tierString){ #ignore any empties
$tier = $service.CreateElement("Tier")
$e = $tierString.Split('[')
$tier.SetAttribute("Name",$e[0])
$VMs = $e[1].Split(",")
foreach ($vmString in $VMs){
if($vmString){
$vm = $service.CreateElement("VM")
$vm.SetAttribute("Name",$vmString)
}
$tier.AppendChild($vm)
}
}
$root.AppendChild($tier)
}
$service.AppendChild($root)
$service.Save(($logPath + "ServiceNames.xml"))
There you have it, nice and neat XML. And your scripts have a clue about themselves.
Friday, June 21, 2013
SCVMM hidden system Service Settings
Lately I have been spending a great deal of time with the System Center Virtual Machine Manger Service model and building Service Templates.
Just understanding what the Service model is, and how it takes the SCVMM VM composition model and brings it up to the enterprise / distributed application level helps you understand where MSFT is headed. To continue this evolution, take a look at the v1 of Desired State Configuration that has been announced in Server 2012 R2. But that was a bit off topic.
In the title I mention “system” Service Settings.
If you have spent any time with building SCVMM Service Templates you know that you can define many settings by using the “@MySetting@” notation. When you deploy your Template and it becomes a running Service whomever is deploying is prompted to fill in the Settings you have defined.
This is excellent, now I have the ability to build a template, give it to you and you can personalize it for your environment. I have one example back here where I built a template for the first domain controller in a domain.
Now, that is all fine and dandy. But what if you need to know ‘something’ about all the other Tiers in the Service. Such as; “who are the machines in Tier Q?” Why; '”Because I need to configure an application on this machine to talk to them.”
Before going farther, understand what when SCVMM executes the application scripts in a Service Template as it is being deployed, the scripts within a single machine all run inside the bubble of that machine. They only know about themselves. But most enterprise applications need to know about something other than them. These are big, scaled-out, distributed applications with multiple server roles that all need to talk to each other.
Well, this is where (courtesy of a MSFT PM, who gave approval for me to write this) I ran across some undocumented Service Settings. These are Service settings that SCVMM fills in during the “Refresh Preview,” instead of prompting your customer; and will process during the deployment.
In my last post I wrote about turning a string into XML. My sample string was from the Service Setting; @ServiceVMComputerNames@.
- @ServiceVMComputerNames@ – Each Tier in the Service and the ComputerName within them. In my mind, this is the handiest. And you can get very similar information from the Azure Service.Runtime within a PaaS VM.
This gives you a string with the name of each Tier in your Service and the ComputerNames within it. It is handy information. In the follow-up post I will give my entire solution to passing and parsing this.
Other system Service settings that SCVMM can directly pass include:
- @ComputerName@ – the computer name assigned to the VM.
- @ServiceName@ – the name of the deployed Service the VM is a member of
- @VMID@ – the GUID of the VM in SCVMM
- @ServiceID@ – the GUID of the Service in SCVMM
There may be more. As far as I can tell, these are not documented at this time. And it takes a bit of work to discover them and the formatting.
Thursday, June 13, 2013
Turning a string into an XML document with PowerShell
This is one of those things that always seems like it should be really easy, and straightforward.
Well, if you are deeply familiar with XML it might be. And If you can pry your string apart into an array using split or regex you have half of the problem tackled.
Lets begin by looking at my string.
$s = "Machine Tier - ScaleOut[Machine03,Machine04,Machine05]Machine Tier[Machine02]"
Ugly thing. That actually contains three elements of data.
The entire string represents a Service. The data outside the brackets represents a Tier. The data inside the brackets is a list of VMs in that Tier.
First, I need to break it all apart.
$a = $s.Split(']');
foreach($b in $a) {
$c = $b.Split('[');
"Tier = " + $c[0];
"VMs = " + $c[1];
}
Tier = Machine Tier - ScaleOut
VMs = Machine03,Machine04,Machine05
Tier = Machine Tier
VMs = Machine02
Tier =
VMs =
Okay, that is close, but not quite there. And still not usable.
First, lets do something about that empty last item in the array.
$a = $s.Split(']');
foreach($b in $a) {
if($b){
$c = $b.Split('[');
"Tier = " + $c[0];
"VMs = " + $c[1];
}
}
And now, break apart the VM name string
$a = $s.Split(']');
foreach($b in $a) {
if($b){
$c = $b.Split('[');
"Tier = " + $c[0];
$VMs = $c[1].Split(",")
foreach ($vm in $VMs){
"VM = " + $vm
}
}
}
Tier = Machine Tier - ScaleOut
VM = Machine03
VM = Machine04
VM = Machine05
Tier = Machine Tier
VM = Machine02
Okay, now I have something that is usable. And now I want to turn that into XML.
I have spent a great deal of time searching on PowerShell and XML. Trying to figure out how to build an XML using PowerShell on the fly in my script. All the examples always start with a TXT based framework of some type that is in turn manipulated by the author. Or a file is read, or objects are queried.
I am sorry, but this is not an example of generating an XML using PowerShell as the title suggests. It is a huge formatted text object that is manipulated. Such a frustrating example to hit over and over again.
Well, I just have this silly string I parsed apart. And I want that to be XML. It already has meaning to the pieces, I just need to make them tags and whatnot.
I mentioned early on that I don’t know much about XML. I read it, transpose it, consume the XML of others’, I never made my own. So, I had to visit the wisdom of a developer friend and make sure that i was doing it ‘correctly’ and it was ‘proper’.
In it simplest sense, to create an empty XML document in PowerShell do this: “$service = New-Object System.Xml.XmlDocument” and you have an XML document. But how do you put things into it.
Okay, this is all about objects, and object manipulation. You don’t just add tags in. You create objects that are of the type $service and you add them back to $service in the correct order.
I began with this:
$service = New-Object System.Xml.XmlDocument
$tier = $service.CreateElement("Tier")
$tier.SetAttribute("Name","My Test Tier")
$vm = $service.CreateElement("VM")
$vm.SetAttribute("Name","My VM Name")
$tier.AppendChild($vm)
$service.AppendChild($tier)
I create the XML Document $service. Then I create an Element of type $service and define a Name and value as an Attribute of that Element. I repeat this and create a $vm element as well.
If you query $service, you find that these things aren’t there. They are three separate objects at this point. They are all share the same object type of $service. But nothing more. Now I assemble them together.
I take the $tier object and I add the $vm object to it as a child. This nests <vm> under <tier> in the XML. I then repeat this adding this updated $tier object to $service as a child.
The above is fine enough. However, I was informed that I was missing a root element. To define the Document.
$service = New-Object System.Xml.XmlDocument
$root = $service.CreateElement("RootElement")
$tier = $service.CreateElement("Tier")
$tier.SetAttribute("Name","My Test Tier")
$vm = $service.CreateElement("VM")
$vm.SetAttribute("Name","My VM Name")
$tier.AppendChild($vm)
$root.AppendChild($tier)
$service.AppendChild($root)
I have been informed that simply doing what I just showed above is still not quite good enough. It meets the requirement of a root element but totally missed on the intent or spirit. We will get back to that.
So, what does this XML document look like? Well, you can step through $service and try to imagine it in your head our you can send it out to a file and open it in notepad.
$service.Save(".\service.xml")
Open that up and you have:
<RootElement>
<Tier Name="My Test Tier">
<VM Name="My VM Name" />
</Tier>
</RootElement>
Now. I have some XML. And I am feeling pretty proud of myself.
Why did I ever do this in the first place? So I could do this:
PS C:\Users\Public\Documents> $service.GetElementsByTagName("Tier")
Name VM
---- --
My Test Tier VM
PS C:\Users\Public\Documents> $service.GetElementsByTagName("VM")
Name
----
My VM Name
Now I have associations that I can look for and query against.
Now, the fun part, meshing those two different activities together as one. And I have the following:
$tiers = $s.Split(']')
$service = New-Object System.Xml.XmlDocument
$root = $service.CreateElement("RootElement")
foreach($tierString in $tiers) {
if($tierString){ #ignore any empties
$tier = $service.CreateElement("Tier")
$e = $tierString.Split('[')
$tier.SetAttribute("Name",$e[0])
$VMs = $e[1].Split(",")
foreach ($vmString in $VMs){
if($vmString){
$vm = $service.CreateElement("VM")
$vm.SetAttribute("Name",$vmString)
}
$tier.AppendChild($vm)
}
}
$root.AppendChild($tier)
}
$service.AppendChild($root)
Now, back to that really lazy root element I created. In practice, that should be some meta information about the XML document itself. If you look at lots of XML you will see things like creation dates, versions, authors, and a name that is somehow descriptive.
After I create the $root object (with a better name) I just update it with a few attributes and I am good to go.
$root.SetAttribute("version","1.0")
$root.SetAttribute("createon",(Get-Date))
$root.SetAttribute("createdby","brianeh")
Now, a really short example of what I can now do with this information.
# query on a specific VM element with a Name equal to "Machine02"
# The Item(0) returns the XML object itself instead of a reference to it. $me = $me.ItemOf(0)
$me = ($service.SelectNodes("//VM[@Name='Machine02']")).Item(0)
# What Tier do I belong to?
$me.ParentNode.Name
# Do I have Siblings or am I an only Child?
$me.NextSibling
$me.PreviousSibling
$me.ParentNode.ChildNodes.Count
Note: be careful. These queries are in XPath format, and they are case sensitive.
You can also simply walk the XML as PowerShell supports that as a ‘.’ notation path.
Friday, June 7, 2013
Handling freakishly long strings from CMD to PowerShell
This is one of those “quick, blog it before you forget it all” type of posts. As well as “hey, I think I get it” and “the things you learn”.
I can thank a particular Microsoft PM for even causing me to look into this. Never would I have expected that I would need to be prepared to handle an “extremely long” string. What do I mean by extremely long? Over 8200 characters, that is what I mean.
To quote the individual that caused me to spend time figuring this out: “I don't recommend this .. because <string in question> can expand to a very large string that will overflow the max # of chars you can pass from cmd.”
That is my entire point here. I am not just in the PowerShell state of euphoria. I have some process that is invoking PowerShell as a command calling my script and feeding it parameters. Open a command prompt and type “PowerShell.exe /?” for a flavor of this world.
Well, what is that maximum number? How big could this be?
Open a command window and start typing. When you think you are done, keep going. You will hit a limit, it is just below 8200 characters. If you try to pass a string to a script and output it, you will be there. I did this:
.\SampleScript.ps1 my incredibly long string where I kept typing and copy and pasting until I ran out of characters.
Within the script I captured the input in a special way and output the length and the string back to a file. Note that param property “ValueFromRemainingArguments” (below) if this is not there, each space in that string gets treated like a new argument and within the script you end up with $args as an array.
Param (
[parameter(Mandatory = $false, ValueFromRemainingArguments = $true)]
$ComputerNames = ""
)
$ComputerNames.getType() | Out-File C:\users\Public\Documents\ComputerNames.txt
$ComputerNames.length | Out-File C:\users\Public\Documents\ComputerNames.txt -Append
$ComputerNames | Out-File C:\users\Public\Documents\ComputerNames.txt -Append
Also buried in this correspondence with this individual was a suggestion to write an executable and capture the string using stdIn, parse it, and then invoke PowerShell from there.
Well, I am becoming a bit of a PowerShell junkie, and I am a lot more comfortable there than with C#. And, I have to think about the poor sole that has to take care of this project when I finish. Why give them some hack of an exe to take care of?
Lets back up. StdIn. What in the world is that? I have never run across that with PowerShell. I am definitely not a developer either.
The best way that I can describe StdIn is a read stream. Instead of passing in a string a stream is passed in and parsed when the end of stream is received. After talking to a developer cohort, I learned that I actually use StdIn in PowerShell quite frequently. Pipelining “in” uses StdIn.
So doing this:
$someString | .\ComputerNames.ps1
Uses the StdIn method for inputting the data.
But wait. Okay, a bit of noise about StdIn and cmd limits. what about the long strings?
Okay, pipeline.
My test was a 24,000+ character string.
$someString = "However you want to make this really, incredibly long. Keep going."
$someString.Length
$someString | .\SampleScript.ps1
But you need to change that param line so that it takes the pipeline.
Param
(
[parameter(Mandatory = $false, ValueFromPipeline = $true)]
$ComputerNames = ""
)
If you want to see what happens the other way, go back and try. If you keep it all in PowerShell it works. But if you call the PowerShell script from cmd it truncates due to the original issue I was warned about.
So, in the end, I did not have to write some exe that only parses this input, I actually used PowerShell instead.
Here is how I invoke:
%WINDIR%\System32\WindowsPowerShell\v1.0\PowerShell.exe -command "'%ComputerNames' | .\SampleScript.ps1 -otherArg 'gibberish'"
Here is my script:
Param
(
[parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromRemainingArguments = $true)]
$ComputerNames = "",
[parameter(Mandatory = $true)]
[string]$otherArg = ""
)
$otherArg | Out-File C:\users\Public\Documents\ComputerNames.txt
$ComputerNames.getType() | Out-File C:\users\Public\Documents\ComputerNames.txt -Append
$ComputerNames.length | Out-File C:\users\Public\Documents\ComputerNames.txt -Append
$ComputerNames | Out-File C:\users\Public\Documents\ComputerNames.txt -Append
Thanks for reading. If you ask why? My only answer is because I have to, I have no other option. We don't always have interactive sessions at our disposal. Sometimes we are headless scripts running under some workflow engine.
==
My developer friend just uncovered the following this dies not support additional parameters to the string and may be able to handle strings that are longer yet:
File Blah.ps1
$c = [Console]::In.ReadToEnd()
"Here"
$c In use you still take your long input and pipe to the script, but the use of ‘reading in’ the input is more literal (for lack of a better description).
Wednesday, June 5, 2013
Discovering and initializing a data volume at VM provision
A few posts back I wrote about using PowerShell to find the DVD drive that a particular installer was attached to and then running that command line installer.
To take that a bit further I have a VM that I am deploying, and that VM has an empty VHDX attached to it.
This VHDX is on the second IDE controller (it needs to be available early in the boot process of the OS). When I provision this VM the first time, I want to find, online, initialize, and format that virtual disk.
$disk = Get-Disk | where {($_.BusType -eq "ATA") -and ($_.IsBoot -eq $false)}
(You could also find the disk if it was on the SCSI controller)
$disk = Get-Disk | where {$_.BusType -eq "SCSI"}
And now for mounting, and formatting the disk.
Set-Disk -InputObject $disk -IsOffline $false
Set-Disk -InputObject $disk -IsReadOnly $false
Initialize-Disk -InputObject $disk -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
$partition = New-Partition -InputObject $disk -UseMaximumSize –AssignDriveLetter
$partition | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Cache" -Force:$true -Confirm:$false
And now you simply continue along with your scripting.
The reason that I capture the new partition to $partition is that there are lots of useful stuff in there for configuring things moving on. Little things like: $partition.DriveLetter are highly useful.
Wednesday, May 1, 2013
Discovering programs on a disk to run silent installers
I recently worked through my version of a PowerShell snip that has to discover in installer and run it.
In my case that installer is on an ISO that is attached to a VM. And my script runs within the VM.
Now, this should be easy, just find the DVD drive, and run the installer.
Well, as always happens, it is not that easy in my case. Come to find out, I could have multiple ISO attached at the moment my script is running, and I need to detect the correct one. I also have to make sure that the installer exists before I try to run it and cause an error. (there is always a need for error handling).
If I didn’t have this mess I could just assume that my ISO is D:\ and put in the path and move on.
First, ask yourself if the ISO will ALWAYS be on D:\? If not, then you need to find the DVD drives and specifically those that have something mounted. From within the running OS, not at some VM management layer.
I do that with the following:
# CD-ROM selects anything in the DVD drive. The size ensures that something is mounted.
$dvdDrives = Get-Volume | where {$_.DriveType -eq "CD-ROM" -and $_.Size -gt 0}
I also have different installers for x86 and x64, so I have to detect the ‘bit-ness’ of the OS and smartly choose the correct installer. A bit of searching turned me to this reliable way:
Switch ([System.IntPtr]::Size)
{
4 {
# x86
}
}
8 {
# x64
}
}
}
Now, I have to build the path so that later on in my script I can run it.
If (Test-Path -Path ($dvd.DriveLetter + ":\Installer\") -PathType Container){
$InstallMedia = Get-ChildItem -Path ($dvd.DriveLetter + ":\Installer\") -recurse -Filter "ServerInstaller.exe"
}
And, before attempting to run it, I need to test that the file really exists. And what is the literal path to the installer. Because how I got here was by testing for the folder structure, not by searching for the individual files (which would take a lot longer).
If ($InstallMedia -ne $null){
If (Test-Path $InstallMedia .FullName){
$InstallPath = $InstallMedia .FullName
}
}
The entire script:
# CD-ROM selects anything in the DVD drive. The size ensures that something is mounted.
$dvdDrives = Get-Volume | where {$_.DriveType -eq "CD-ROM" -and $_.Size -gt 0}
# Since a VM could have more than one DVD drive, we need to find the correct one.
foreach ($dvd in $dvdDrives){
Switch ([System.IntPtr]::Size)
{
4 {
If (Test-Path -Path ($dvd.DriveLetter + ":\x86\") -PathType Container){
$Media = Get-ChildItem -Path ($dvd.DriveLetter + ":\x86\") -recurse -Filter "ServerSetup.exe"
}
}
{
If (Test-Path -Path ($dvd.DriveLetter + ":\x64\") -PathType Container){
$Media = Get-ChildItem -Path ($dvd.DriveLetter + ":\x64\") -recurse -Filter "ServerSetup.exe"
}
}
}
If ($Media -ne $null){
If (Test-Path $Media.FullName){
$FilePath = $Media.FullName
}
}
}
Start-Process -FilePath $FilePath –ArgumentList “/quiet “ -Wait -NoNewWindow
"Done waiting for the installer"
Wednesday, February 13, 2013
SCVMM Service Template for the first DC in a Forest – the bits
So here goes nothing. Why not hand it over just to see how this Service Template Export and Import process really works.
In theory, you simply download my Service Template and then Import it. It will give you the custom application resource folder and the scripts.
You need to bring the Server 2012 evaluation VHD image and place it in your Library.
Be sure to hook your logical network settings, connect to the VHD image.
When you configure your deployment you should have to fill in the domain FQDN, NetBIOS name, and recovery password. Then click go.
That is the theory.
Why not give it a go?
I ask one thing though, if you download and try – can you please leave comments and feedback about the experience.
Thanks!
Tuesday, February 12, 2013
SCVMM Service Template for the first DC in a Forest – part 2
Okay, so I posted the traditional way of handling this, with a BATCH file.
But, in reality all I did was sue a BATCH file to in turn process a PowerShell script. I considered this silly. There must be a way to process the PowerShell script without having to use the BATCH script.
I mean, come on. This is Server 2012 I am using and PowerShell v3. Yes, I know there are some advanced things that ca be done with BATCH scripting (I have done some in my history), but think out of the box here.
So, I spent bunches of time playing around with this (so you wouldn’t have to (if you stumbled on my post)).
In the end, it wasn’t that difficult, just had to think about things a bit differently.
Oh, and one important thing I left out of my previous post. Use a local administrator Run As account for adding the local administrator admin credentials to the OS and the same Run As account a second time to process the scripts.
So, here is the script the PowerShell way:
param (
[string]$domainName,
[string]$netbiosName,
[string]$safeModePass
)# Build a domain controller and the test domain.
# Add the RSAT tools
Add-WindowsFeature RSAT-AD-Tools# Add the features
Add-WindowsFeature AD-Domain-Services -IncludeAllSubFeature -IncludeManagementTools
Add-WindowsFeature DNS -IncludeAllSubFeature -IncludeManagementTools
Add-WindowsFeature GPMC -IncludeAllSubFeature -IncludeManagementTools# convert the password to a secure string as required
$secPass = ConvertTo-SecureString -String $safeModePass -AsPlainText -Force# Create the Forest and Domain
Install-ADDSForest -CreateDnsDelegation:$false -DomainMode Win2012 -DomainName $domainName -DomainNetbiosName $netbiosName -ForestMode Win2012 -InstallDns -Force -SafeModeAdministratorPassword $secPass
I know what you are thinking, that can be shortened. And my reply; yes, it can. And you advanced folks, go right ahead.
Now, in the Application Configuration of the Tier in the Service. Two pre-install scripts.
The first pre-install script is to set script execution to RemoteSigned:
The executable program is: %WINDIR%\System32\WindowsPowerShell\v1.0\PowerShell.exe
And the Parameters are: -command set-executionpolicy remotesigned –force
(I don’t have a Run As account defined BTW).
The second pre-install script is everything above. But those are included in the Custom Resource Package as a .ps1 file.
The executable program is the same. The Parameters are different: -file .\DomainController.ps1 @DomainName@ @DomainNetbiosName@ @SafeModeAdministratorPassword@
And the Run As account is my local admin run as account profile. And the timeout needs to be turned up to about 600 seconds.
That is it. I tried it a few times. It works.
Monday, February 11, 2013
SCVMM Service Template for the first DC in a Forest – part 1
I am teaching myself about Service Templates in SCVMM and I always like to take something that I am doing and repurpose it.
Today, I finally had success with taking the PowerShell scripts behind creating a new domain controller in a new forest and automating that using the SCVMM Service Template model.
Oh my (quoting George Takei). What an interesting adventure this has been.
So, if you are looking for a real world examples of building your own Template, here is it. No references to Pet Shop here.
Also, this is not a walkthrough or a point and click post. I am expecting you to have some familiarity with setting up SCVMM and read all the other blogs about building an SCVMM Service Template. I am going straight to the peculiar things that you need to be aware of or do to make this happen.
First:
I begin with the Server 2012 evaluation VHD (nice and small, prepared with sysprep, no baggage.) and I place that in my SCVMM Library.
Second:
And this is (what I think is) the key – I build some scripts. The SCVMM Library comes with a few “Application Frameworks” and these are Custom Resources. Server App-V and WebDeploy are delivered this way.
How do you do this? Go to your SCVMM Library share, and model the built in ones.
Create a Folder called MyFoo.cr. Add a file to that called “SCVMMCRTag.cr” - Don’t copy an existing one, create one.
Add your scripts to this folder.
The Scripts:
Welcome back to the world of BATCH. If you have been here before, you will recall it fondly. If you have never been here, you must be relatively young. There are some excellent web sites with some excellent BATCH command references. Just search a bit (there aren’t lots, so you find them rather quickly).
Why batch – look at the MSFT included stuff. Fun with BATCH. And it ‘just works’.
So here is my trick: Running PowerShell commands from a BATCH script:
echo Set the Execution Policy to RemotedSigned
%WINDIR%\System32\WindowsPowerShell\v1.0\PowerShell.exe -command set-executionpolicy remotesigned –force
And for this exercise I can install all the required features of Active Directory and DNS:
REM echo Add the RSAT tools
%WINDIR%\System32\WindowsPowerShell\v1.0\PowerShell.exe -command Add-WindowsFeature RSAT-AD-ToolsREM echo Add the Windows Features
%WINDIR%\System32\WindowsPowerShell\v1.0\PowerShell.exe -command Add-WindowsFeature AD-Domain-Services -IncludeAllSubFeature –IncludeManagementTools%WINDIR%\System32\WindowsPowerShell\v1.0\PowerShell.exe -command Add-WindowsFeature DNS -IncludeAllSubFeature –IncludeManagementTools
%WINDIR%\System32\WindowsPowerShell\v1.0\PowerShell.exe -command Add-WindowsFeature GPMC -IncludeAllSubFeature -IncludeManagementTools
And this is where BATCH fails. This needs to be totally unattended. And the cmdlet to create the AD Forest requires a secure string. I can’t do that in BATCH. I need to actually use a script (or call the command and add my script as a script block).
This PowerShell script will be in the same folder as the BATCH script (MyFoo.cr).
param (
[string]$domainName,
[string]$netbiosName,
[string]$safeModePass
)# convert the password to a secure string as required
$secPass = ConvertTo-SecureString -String $safeModePass -AsPlainText -Force# Create the Forest and Domain
Install-ADDSForest -CreateDnsDelegation:$false -DomainMode Win2012 -DomainName $domainName -DomainNetbiosName $netbiosName -ForestMode Win2012 -InstallDns -Force -SafeModeAdministratorPassword $secPass
Okay, back to the batch script. You need to properly call the PowerShell script from the BATCH script.
REM Running the PowerShell Domain Controller script
%WINDIR%\System32\WindowsPowerShell\v1.0\PowerShell.exe -file %~dp0\DomainController.ps1 %domName% %domNBios% %recoPass%
But what are those “%” thingies? Parameters. I pass parameters to the BATCH script, which are in turn passed to the PowerShell script. All because of the Secure String.
Now, setting this in the Service Template:
The executable program is cmd.exe (because we are using the command prompt)
And the parameters are: /q /c DCInstall.cmd @DomainName@ @DomainNetbiosName@ @SafeModeAdministratorPassword@
The /q is for ‘quiet’ and the /c defines the ‘command’ – the /c needs to be the last entry of the line. The @thing@ are the parameters that SCVMM will prompt you for when you deploy.
And the Run As account is my local admin run as account profile. And the timeout needs to be turned up to about 600 seconds.
And the Script resource package is that folder you created that you put the scripts into. This gets copied to your VM via BITS (the VM OS does a pull from the Library share BTW), executed, and then deleted on success.
It should look something like this:
And the Custom Resource:
Next post, a twist. Who wants to call the command prompt and relive the glory days of BATCH all the time? Why not use only PowerShell??