System Center Virtual Machine Manager vCheck
System Center Center Virtual Machine Manager (SC VMM) is part of the Microsoft System Center family, used to manage virtual infrastructures. The 2008 R2 version supports managing both Microsoft Hyper-V and VMware (communicating via VMware vCenter), while the 2012 version which was released in April 2012 added support for Citrix XenServer. Among the added benefits of using SC VMM for managing your hypervisor infrastructure is capabilities such as Physical to Virtual (P2V) conversion, creating virtual machines based on templates as well as having a centralized console for management. The 2012 release also added capabilities for managing private cloud infrastructures, where the managed hypervisors will be abstracted under the new Fabric bar:
There are many additional changes in the 2012 release, including the ability to manage services, storage, networking, cluster creation and many more. If you are interested in learning more I would recommend you to watch the TechNet Edge video Virtual Machine Manager 2012: Technical Overview as well as dig into the product documentation (link in the Resource section below).
Most, if not all, Hyper-V production environments I have worked with use SC VMM for management. One thing that many administrators may miss however, is a quick glance at the status of the environment. You do of course see the status of the hosts and virtual machines by opening the console, but other things such as old checkpoints and the use of dynamic virtual hard disks instead of Fixed size virtual hard disks is not easily discoverable. Since SC VMM is based on Windows PowerShell (for example, you get a “View Script” button showing the complete PowerShell code when creating a new virtual machine), gathering this information and presenting it in a report is not so difficult. Let me introduce the SC VMM vCheck:
SC VMM vCheck
If you work with both PowerShell and VMware, there is a good chance you have seen and used Alan Renouf`s very useful vCheck script:
vCheck is a vCenter checking script, the script is designed to run as a scheduled task before you get into the office to present you with key information via an email directly to your inbox in a nice easily readable format.
For more information and detailed usage guidance, see this article.
Beginning with the 6.0 release of vCheck, Alan added the support for Plugins, making it easier for the community to contribute with their own checks. I had several snippets of PowerShell code for checking various things such as VMs with snapshots, so when I saw the new Plugins capability added to vCheck I thought it would be a good opportunity to provide a SC VMM Plugin. Here is the initial checks in the SC VMM Plugin for vCheck:
- SC VMM Connection Plugin
- General Information
- SC VMM Basic Host Information
- Hyper-V Cluster Shared Volumes
- Hyper-V VMs with snapshots
- SC VMM New VMs
- SC VMM VM Operating System Statistics
- Hyper-V VMs with mounted ISO
- Hyper-V Legacy VM NIC Adapters
- Hyper-V VMs with static MAC address
- SC VMM Dynamically Expanding Disks
- SC VMM Dynamically Expanding Disks Consolidation Forecast
- Hyper-V Integration Components Version
- Hyper-V 2008 R2 SP1 Capacity Report
As you can see I have divided the plugins into two categories, one for SC VMM which works across all supported hypervisors, as well as some specific for Hyper-V.
vCheck contains a ps1-file named GlobalSettings.ps1, where you can specify the name of the SC VMM server, e-mail settings (if you want the report e-mailed to you) as well as HTML color codes. Here you can see two examples of the same report using different colors:
You can download the plugin from this link:
If you want to add a new Plugin, all you need to do is to create a ps1-file in the Plugins-folder, preferably in a subfolder. Then you add the following 7 variables at the top of the file, and your code at the bottom. The main script will pick up objects from the Plugins, so make sure you output objects with only the desired properties. For example Get-VM | Select-Object name.
We can look at the VM Operating System Statistics as an example:
$Title = "VM Operating System Statistics" $Header ="VM Operating System Statistics" $Comments = "VM Operating System Statistics" $Display = "Table" $Author = "Jan Egil Ring" $PluginVersion = 1.0 $PluginCategory = "SC VMM" # Start of Settings # End of Settings $VMs | Group-Object operatingsystem | Select-Object name,count | Sort-Object count -Descending
This is actually a PowerShell “one-liner” turned into a vCheck Plugin. Note that the $VMs variable contains all virtual machines managed by SC VMM and is created in the SC VMM Connectivity Plugin. The settings section is not used by the above plugin, however, this is a section intended for Plugin specific settings such as the threshold for how old snapshots you want to include in the report.
Over time the SC VMM Plugin will hopefully grow by added community contributions. If you want to contribute, or have suggestions for new Plugins, please add them to the SC VMM Plugin site`s comment section, as more people will discover them there compared to this article.
If you want to hear more about the SC VMM vCheck I was interviewed to talk about it on the Get-Scripting podcast, you can download the episode from here.
Resources
System Center Virtual Machine Manager 2012 on Microsoft TechNet
System Center Virtual Machine Manager 2008 and 2008 R2 on Microsoft TechNet
Automate a task in System Center Virtual Machine Manager 2012 based on the “View Script” button
Working with scheduled tasks from Windows PowerShell
Traditionally Windows administrators is used to work with the Windows Task Scheduler using the command line utilities at.exe and schtasks.exe. Schtasks.exe was introduced as a replacement for at.exe in Windows XP/Windows Server 2003, while at.exe still exists in the latest Windows versions for backwards compatibility.
Both of the utilities works in Windows PowerShell, but they both produce text, not objects. This means we can not use for example the object manipulation cmdlets Where-Object and Select-Object on the output from these utilities. There are some tricks you can use to get the output from external applications in PowerShell into objects. An example on this is the /fo parameter on schtasks.exe, which allow us to specify the format of the output from the command. Since PowerShell has a ConvertFrom-Csv cmdlet, this means we can do this to get all scheduled tasks in object form:
schtasks.exe /query /fo csv | ConvertFrom-Csv
This allows us to use for example the Where-Object cmdlet to filter the output based on the Status-property:
To make this utilities behave more PowerShell friendly we could create custom functions which for example wraps schtasks.exe /create in a New-Task function, but still it would require a lot of work to parse the text output into custom objects.
Task Scheduler 2.0 which was released in Windows Vista and Windows Server 2008 introduced a COM-interface. This makes possible to access the Task Scheduler interface using PowerShell: New-Object -ComObject Schedule.Service
By using this COM-object we can access the Task Scheduler Scripting Objects, which produces objects rather than text. Of course this still requires a bit of work and the need to read quite a bit of documentation in order to make it user friendly for an administrator. Luckily the PowerShell team released a TaskScheduler module which builds on this COM-object as part of the PowerShell Pack, released as part of the Windows 7 Resource Kit.
When you have installed the MSI-file from the PowerShell Pack download page, you can import the TaskScheduler module and have a look at the available functions:
We can for example use the Get-ScheduledTask function to retrieve all scheduled tasks on the local computer:
This is an example of how to schedule a PowerShell script to run every day at 06.00:
# Import the module Import-Module TaskScheduler # Create a new task $task = New-Task $task.Settings.Hidden = $true # Add an action and a trigger Add-TaskAction -Task $task -Path C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -Arguments "-File C:\MyScript.ps1" Add-TaskTrigger -Task $task -Daily -At "06:00" # Register the task Register-ScheduledTask -Name "PowerShell - MyScript.ps1" -Task $task
When the above example is run we can find the new scheduled task in the Task Scheduler:
Or of course by using the Get-ScheduledTask function:
Instead of configuring a script file (ps1-file) to be the task action, we could have specified a script block to the Add-TaskAction function:
Add-TaskAction -Task $task -Script {“Hello World”}
Then the script block would be passed as an encoded command to powershell.exe:
-encodedCommand IgBIAGUAbABsAG8AIABXAG8AcgBsAGQAIgA=
An advantage of this approach is that we do not need to save our code to disk in advance to configuring it as a scheduled task.
Scheduled jobs in PowerShell 3.0
With the new version of Windows coming out later this year, we will also get a new version of Windows PowerShell. One of the new features in PowerShell 3.0 is a new module called PSScheduledJobs, which allows us to schedule jobs natively from within PowerShell. In addition, the output and results from the job is actually stored so that we can retrieve them after the job is executed, just like we could with background jobs in PowerShell 2.0.
The following information and examples is based on the Windows PowerShell 3.0 version available in the Windows 8 Consumer Preview.
Here we can see the cmdlets available in the PSScheduledJob module:
The following example is using the PSScheduledJob module to do the same as the previous example which used the TaskScheduler module from the PowerShell Pack:
# Import the module (not necessary due to the new module autoloading in PowerShell 3.0) Import-Module PSScheduledJob # Add a job option $joboption = New-ScheduledJobOption -HideInTaskScheduler # Add a trigger $trigger = New-JobTrigger -Daily -At "06:00" # Register the job Register-ScheduledJob -Name "PowerShell - MyScript.ps1" -Trigger $trigger -FilePath C:\MyScript.ps1 -ScheduledJobOption $joboption
If we want to specify a script block instead of a script file, we can use the ScriptBlock parameter instead of the FilePath parameter of Register-ScheduledJob:
Register-ScheduledJob -Name “PowerShell – MyScript.ps1” -Trigger $trigger -ScriptBlock {“Hello World”}
If we look at the new task that is created from the example above we can see the following:
The arguments passed to powershell.exe:
-NoLogo -NonInteractive -WindowStyle Hidden -Command “Import-Module PSScheduledJob; $jobDef = [Microsoft.PowerShell.ScheduledJob.ScheduledJobDefinition]::LoadFromStore(‘PowerShell – MyScript.ps1’, ‘C:\Users\<username>\AppData\Local\Windows\PowerShell\ScheduledJobs’); $jobDef.Run()”
We can see that the job is stored in a separate folder in the current user`s AppData-folder:
The job is defined in the ScheduledJobDefinition XML-file, and there is an empty sub-folder named Output. If I manually start the job, we can see that the following is added to the Output folder:
Next I want to retrieve the results of the job, not by looking in the XML-files, but by using the Get-Job cmdlet. There is a gotcha to be aware of regarding this. If you open a new PowerShell session and run Get-Job, the scheduled jobs created using the PSScheduledJob cmdlets will not be available. You have to first import the PSScheduledJob module and then run Get-Job. By using Receive-Job we can get the results from our job:
To read about the reason for this in addition to more details about the new module you can read the “Scheduling Background Jobs in Windows PowerShell 3.0” article on the Windows PowerShell Team Blog.
Resources
How to Schedule a PowerShell Script
Windows PowerShell Scheduled Job Module
Download TechNet Edge videos using Windows PowerShell
I recently got a question on Twitter if I could create a PowerShell script to download files from TechNet Edge. I thought this would be useful for many other people as well, so I have spent some time this weekend working on this script. I wanted to make the script as generic and re-usable as possible, so even though I started hardcoding it for downloading files from a given RSS-feed from TechNet Edge, I later on generalized it to make it easy to re-use on other websites as well. The script is called Get-WebVideoFile.ps1, and is available on both PoshCode and the Microsoft TechNet Script Center.
How to use the script
1) The script requires PowerShell 2.0 and you must have configured your PowerShell execution policy in order to run the script. Run $host.version from PowerShell to see what version you are running. If you need help configuring the execution policy or running the script, see “Running Windows PowerShell Scripts” on Microsoft TechNet.
2) Download the script from here (PoshCode) or here (Microsoft TechNet Script Center).
3) Find the RSS URL for the category you want to download files from. On TechNet Edge you can find an overview of all categories (called tags) on this link.
In this example I have chosen the Microsoft Virtual Academy tag:
You can then find the RSS URL by clicking on the RSS icon to the right of the title on the top of the page.
4) Specify the URL on the RssUrl parameter when running the Get-WebVideoFile script:
You will get a progress bar which shows the progress for the current file:
Remember that you can also use Windows PowerShell ISE (or any 3rd party host) instead of the PowerShell console host:
More information about the script
Comment based help
At the top of the script you can see that I have added comment based help. This means you can use Get-Help to show information and example usage of the script:
You can read more about comment based help in this TechNet Magazine Article by Don Jones, as well as in the help topic about_Comment_Based_Help.
Parameters
RssUrl – This is the only mandatory parameter, which provides the script the RSS feed to go through. The script download the HTML code from each RSS entry`s link, and look for a URL that matches the regular expression.
Destination – The folder the files will be downloaded to. If not specified the files will be downloaded to the current user`s Video-folder (for example C:\Users\Username\Videos\).
UseOriginalFileName – This is a switch parameter to specify that you want to use the original file names for the downloaded files. On TechNet Edge most video files got a non-descriptive name, for example e1ef34ca-cdef-463d-9c3f-94aa606bc6ab.wmv. Because of this I set the default behavior of the script to use the RSS item title as the file name.
UrlRegex – This is the regular expression that will be used to search for the video URL from the HTML code from each RSS item link. You can re-use the default regex and replace “wmv” with for example “mp4” if you want to download the MP4-versions of the videos. If you are interested in learning about Regular Expressions, I can recommend the book “Master Regular Expressions” by Jeffrey E.F. Friedl. If you want a tool to help you build the regular expression I can recommend RegexBuddy.
Verbose – This is not a parameter I have added, it is one of the common parameters in PowerShell which I want to highlight since I have added some verbose information. For example if the regular expression does not match, you can see it in the verbose output. This is a switch parameter, so you can add “-Verbose” to the script parameters to use it. You can also read more about the Verbose parameter in the about_CommonParameters help topic.
The download process
Initially when creating the script I started using the DownloadFile method of the Net.WebClient class in .NET. This worked fine, but did not provide any progress status during download. This makes it hard for the user of the script to know how long the download will take, and if anything is happening at all. I then turned to Background Intelligent Transfer Service (BITS), and the Start-BitsTransfer cmdlet which provides progress status as well as other features. For some reason the BITS jobs failed with the error message “HTTP status 404: The requested URL does not exist on the server.”. I am not sure why, but this may be because the server-side on TechNet Edge downloads does not support the necessary HTTP Requirements.
I then got a tip from Joel Bennett about his Get-WebFile function available on PoshCode. This function met my requirements, and is included at the top of the script.
The script will check the destination folder to see if the file is already downloaded, which means you can run the script several times without downloading files you already have. This is also useful when running the script as a scheduled task, which may be of interest if you want to automatically download new videos in a specific category.
Related resources
MMS 2012 Download Sessions – Offline Viewing (PowerShell script to download sessions from MMS 2012)
Automate a task in System Center Virtual Machine Manager 2012 based on the “View Script” button
System Center Virtual Machine Manager has since the beginning had excellent automation capabilities in form of Windows PowerShell. In fact, everything that is performed in the SC VMM Console generates a PowerShell command which is being invoked as a job. A very useful feature in the SC VMM Console is the “View Script” button, which shows you the actual PowerShell commands that is being run. To show you how you can leverage this functionality, I am going to show you how I used it to automate one part of a new deployment.
Provisioning Logical Networks in System Center Virtual Machine Manager 2012
In System Center Virtual Machine Manager 2012 there is a concept called Logical Networks, which is defined as part of the Fabric. You can read more about Logical Networks in this article by the SC VMM team. Step-by-step guidance on how to create and assign a logical network is available in this TechNet Wiki guide by Microsoft MVP Kristian Nese.
What I want to show you in this article is how you in an easy manner can automate the process of creating and assigning virtual networks. I recently had to provision close to 100 logical networks in a new deployment, and as you can read in the mentioned articles I first needed to create the Logical Networks and then go to the Properties of every virtualization host and assign the correct Logical Network to the correct physical NIC.
In my scenario that would mean more than 1000 mouse clicks if doing the task manually. Next I will show you how to automate the creation and assignment of logical networks.
Create the Logical Network
Start the “Create Logical Network” wizard from the Fabric pane in VMM:
Creating a Network Site is not mandatory, but this is something I needed in my scenario:
Click on the “View Script” button on the Summary page (click Cancel in the wizard afterwards):
You will be presented with a PowerShell script in a Notepad window. This is the script I got:
$logicalNetwork = New-SCLogicalNetwork -Name "Backend" $allHostGroups = @() $allHostGroups += Get-SCVMHostGroup -ID "0e3ba228-a059-46be-aa41-2f5cf0f4b96e" $allSubnetVlan = @() $allSubnetVlan += New-SCSubnetVLan -VLanID 0 New-SCLogicalNetworkDefinition -Name "Oslo" -LogicalNetwork $logicalNetwork -VMHostGroup $allHostGroups -SubnetVLan $allSubnetVlan -RunAsynchronously
This above script is the what I will use as the template for my new Logical Network provisioning script. A common approach for creating multiple items is using a foreach loop, but first we need some data which the loop will iterate over. Ìf you have all your Logical Networks defined in an Excel spreadsheet, which was the case for me, you can save the spreadsheet as a comma-separated file and use that as the data source in the script. In PowerShell it is also possible to use a broad number of data sources, for example a SQL database.
After creating a CSV-file I customized the column headers in the file, which will serve as the property names for the imported objects. Here is a sample CSV-file from my lab environment:
Name,Description,HostGroup,VlanID,Site,VMHostAdapterName
Backend,"This is the corporate backend network","All Hosts",0,Oslo,Trunk
As you can see in my customized script there is not very much that is changed:
$networks = Import-Csv -Path C:\Import\VMM_logical_networks.csv foreach ($network in $networks) { $logicalNetwork = New-SCLogicalNetwork -Name $network.Name -Description $network.Description $allHostGroups = @() $allHostGroups += Get-SCVMHostGroup -Name $network.HostGroup $allSubnetVlan = @() $allSubnetVlan += New-SCSubnetVLan -VlanID $network.VlanID New-SCLogicalNetworkDefinition -Name $network.Site -LogicalNetwork $logicalNetwork -VMHostGroup $allHostGroups -SubnetVLan $allSubnetVlan -RunAsynchronously }
A brief overview of the changes:
- On the first line we import the contents of the CSV-file using the Import-Csv cmdlet.
- Next we iterate over each entry in the CSV-file by using a foreach loop from line 3 to 13. This means that line 4 to 12 will be repeated for every line in the CSV-file.
- The last customization is made to the parameters of the cmdlets in the script. For example the Name property of New-SCLogicalNetwork is the value ($network.Name) of the Name property of the imported CSV objects.
Assigning the Logical Network
The final task is to assign the Logical Networks we created to the correct physical NICs of the virtualization hosts. Again we start by doing the task in the SC VMM Console:
Go into the Properties dialog of one of the virtualization hosts, select Hardware and then select a NIC.
Press the check box of two or more Virtual Networks, and the click the “View Script” button (click Cancel in the Properties dialog afterwards):
We can see that the generated script is a bit more complicated than the previous one:
$vmHost = Get-SCVMHost -ID "82b7c0f3-ed2e-4dcc-8b05-40765dba712e" -ComputerName "hpv-jr-02" $vmHostNetworkAdapter = Get-SCVMHostNetworkAdapter -Name "Broadcom NetXtreme Gigabit Ethernet" -VMHost $vmHost # Backend $logicalNetwork = Get-SCLogicalNetwork -ID "b036b32a-8139-4b0e-babc-cda1e98ef2e7" Set-SCVMHostNetworkAdapter -VMHostNetworkAdapter $vmHostNetworkAdapter -AddOrSetLogicalNetwork $logicalNetwork -JobGroup "4af63cdf-ea00-4647-b554-cc7167070f33" # LAN $logicalNetwork = Get-SCLogicalNetwork -ID "292c0075-f81d-4af9-81f5-c775c5bcf90a" Set-SCVMHostNetworkAdapter -VMHostNetworkAdapter $vmHostNetworkAdapter -AddOrSetLogicalNetwork $logicalNetwork -JobGroup "4af63cdf-ea00-4647-b554-cc7167070f33" Set-SCVMHostNetworkAdapter -VMHostNetworkAdapter $vmHostNetworkAdapter -Description "" -JobGroup "4af63cdf-ea00-4647-b554-cc7167070f33" -VLanMode "Access" -AvailableForPlacement $true -UsedForManagement $true Set-SCVMHost -VMHost $vmHost -JobGroup "4af63cdf-ea00-4647-b554-cc7167070f33" -RunAsynchronously
We will continue to use the same CSV-file as we did when creating the logical networks. You might have noticed that there was one column in the CSV-file we did not use in the first script: VMHostAdapterName. This is a property which will be used to assign the logical networks to a physical NIC on a virtualization host, based on the connection name of the network adapter. For example, I have renamed a network adapter from “Local Area Connection” to “Trunk #1”. In this case the VMHostAdapterName in the CSV-file should be “Trunk #1” for the Virtual Networks which is mapped to this physical NIC. This network adapter is configured in trunk mode, which means it is assigned multiple VLANs and thus may contain multiple Virtual Networks.
This way of performing the mapping between a physical NIC and a Virtual Network is a design decision I took when creating the script, although you may choose to do the mapping based on other properties, such as the IP-address range of the NIC. Since the environment I was working with had a consistent naming convention for the virtualization host network adapters I found this to be the most appropriate way of doing the mapping.
Before the walkthrough, I will first show you the customized version of the script the VMM Console generated:
$vmhosts = Get-SCVMHost $networks = Import-Csv -Path C:\Import\VMM_logical_networks.csv foreach ($vmhost in $vmhosts) { # Create a Job Group ID $JobGroupID = [Guid]::NewGuid().ToString() foreach ($network in $networks) { $vmHostNetworkAdapter = Get-SCVMHostNetworkAdapter -VMHost $vmhost | Where-Object {($_.LogicalNetworks | Select-Object -ExpandProperty Name) -contains $network.VMHostAdapterName} if ($vmHostNetworkAdapter) { $logicalNetwork = Get-SCLogicalNetwork -Name $network.name Set-SCVMHostNetworkAdapter -VMHostNetworkAdapter $vmHostNetworkAdapter -AddOrSetLogicalNetwork $logicalNetwork -JobGroup $JobGroupID } } Set-SCVMHost -VMHost $vmhost -JobGroup $JobGroupID -RunAsynchronously }
A brief overview of the changes:
- On the first line we retrieve the virtualization hosts we want to iterate over. You may choose to limit the scope to a specific host group by using the –VMHostGroup parameter.
- On the second line we import the contents of the CSV-file like we did in the previous script
- On line 4 to 24 we create a foreach loop to iterate over all the virtualization hosts we initiated in the $vmhosts variable
- The next thing we do is creating a job group (a GUID), which will make VMM perform all the following changes in a single job. This means that a single job will be run for each virtualization host when adding 100 Virtual Networks, instead of 100 jobs for each virtualization host. The concept of using Job Groups is described in this article in the VMM 2012 documentation on Microsoft TechNet.
- On line 9-19 we iterate over all of the virtual networks in the CSV-file, and try to find a network adapter with a name equal to the VMHostAdapterName property of the Virtual Network defiend in the CSV-file.
- Next, an if statement is used to determine if a network adapter was found. If the statement is evaluated to $true the actual change is being committed to the network adapter (this is the same as ticking the checkbox in the dialog box in the screenshot above).
- When the inner foreach loop is finished the job will be commited by the command on line 21.
- The same steps will be repeated until the outer foreach loop is finished.
The last recommendation I want to provide is to copy the script generated by the “View Script” button to a PowerShell editor like the Windows PowerShell ISE:
As you can see the script in the above screenshot is much easier to read than the following due to features such as syntax coloring and brace matching:
Resources
Getting Started with Virtual Machine Manager
System Center: Virtual Machine Manager Engineering Team Blog
2012 Scripting Games
The Microsoft Scripting Guys is hosting the 2012 Scripting Games April 2 – April 13 2012. The Scripting Games started out several years ago as a scripting competition based on VBScript. After the introduction of Windows PowerShell the Scripting Games did both a VBScript and a PowerShell track for a few years, until last year when VBScript was phased out in advantage of Windows PowerShell.
We can see the same thing happening in terms of automation capabilities in both Microsoft and non-Microsoft products. When we also look at the massive PowerShell capabilities coming in Windows 8 and Windows Server 8, we can see that PowerShell has turned into an essential skill for IT Professionals.
The Scripting Games is an excellent opportunity to get started with the scripting capabilities in Windows PowerShell. If you are new to PowerShell, the Beginner track is the recommended track for your first year participating in the Scripting Games. For more experienced scripters there is an Advanced track with more challenging tasks. Note that using features in the current beta of Windows PowerShell 3.0 is allowed in the Scripting Games submissions.
For those who want an introduction to PowerShell before the Scripting Games begin, Scripting Guy Ed Wilson will host a 5-series Live Meeting called “Windows PowerShell for the Busy Admin” beginning March 12, 2012.
Like last year, I will participate in the Scripting Games as a judge, which will be great fun! For those interested in learning PowerShell I highly recommend to take the time to participate in the Scripting Games, as it is a great learning experience. If your on Twitter, remember to use the 2012 Scripting Games Twitter tag #2012SG.
Good luck to all participants!
2012 Scripting Games Resources
2012 Windows PowerShell Scripting Games: All Links on One Page
2012 Scripting Games: Frequently Asked Questions
2012 Scripting Games Judging Criteria Revealed
Announcing the PowerShell Judges for the 2012 Scripting Games
TechNet Radio podcast: Announcing the 2012 Scripting Games
PowerScripting Podcast: Episode 177 – Scripting Guy Ed Wilson on the 2012 PowerShell Scripting Games
How to configure AntiAffinityClassNames in Failover Clustering using Windows PowerShell
AntiAffinityClassNames is a cluster group property in Windows Failover Clustering used to identify cluster groups that should not be hosted on the same node. When working with clustered Hyper-V environments there is a 1:1 relationship between a cluster group and a virtual machine, and thus we can configure Anti-Affinity for virtual machines using the AntiAffinityClassNames property. For more information, see the links in the resource section in the bottom of the article.
What I want to show in this article is how to configure the AntiAffinityClassNames property using PowerShell. Most articles I have seen in regards to AntiAffinityClassNames covers how to use cluster.exe to configure the property, and since the cluster.exe was deprecated in Windows Server 2008 R2 (it`s still available as a deprecated command) it might be a good idea to learn how to configure this using the new Failover Clustering PowerShell module. In addition you also get other benefits available in PowerShell such as pipelining, export/import to/from different data sources, grouping and so on.
AntiAffinityClassNames is a property of a cluster group, but before we configure the property we need to look at what type it is. We can do this by piping the output of Get-ClusterGroup to the Get-Member cmdlet:
We can see in the definition of the property that it is a System.Collections.Specialized.StringCollection type. This means we can not just pass a text string as the property value, we need to create a object of the correct type. The reason this property is not just a System.String (text string) is that a Cluster Group can have more than one AntiAffinityClassNames values. System.Collections.Specialized.StringCollection has a method called Add which can be used to to add values to the collection. Here is a complete example on how to create such an object, add a couple of values to it and at the end assign it to the AntiAffinityClassNames property of a cluster group:
$value = New-Object System.Collections.Specialized.StringCollection $value.Add("Domain Controllers") $value.Add("Infrastructure Server Group") (Get-ClusterGroup "SCVMM SRV01 Resources").AntiAffinityClassNames = $value
Note that this will overwrite any existing values. As we can see it is not very hard to configure a new value, but it might be harder if you want to preserve existing values remove single items of the existing value and so on. In order to make this easier I have created a PowerShell module which contains 3 script cmdlets for managing AntiAffinityClassNames:
As you can see in the cmdlet help there is support for pipeline input, which makes it possible to configure the AntiAffinityClassNames values based on input such as a CSV-file:
I have also provided examples for the script cmdlets:
Installing the AntiAffinityClassNames module
Download and unzip AntiAffinityClassNames.zip in the following location: %userprofile%\Documents\WindowsPowerShell\Modules\AntiAffinityClassNames
Alternatively you may save the module in any of the folders in the $Env:PSMODULEPATH variable or import the module from a custom path (Import-Module <path>\AntiAffinityClassNames). Here is some more examples to get you started:
# Import the module Import-Module AntiAffinityClassNames # Example usage of the Get-AntiAffinityClassNames script cmdlet Get-AntiAffinityClassNames -Cluster cluster01.domain.local Get-AntiAffinityClassNames -Cluster cluster01.domain.local -ClusterGroup "SC VMM SRV01 Resources" Get-AntiAffinityClassNames -Cluster cluster01.domain.local | Export-Csv -Path c:\temp\AntiAffinityClassNames_cluster01.csv -NoTypeInformation # Example usage of the Set-AntiAffinityClassNames script cmdlet Set-AntiAffinityClassNames -Cluster cluster01.domain.local -ClusterGroup "SC VMM SRV01 Resources" -Value "Domain Controllers" Set-AntiAffinityClassNames -Cluster cluster01.domain.local -ClusterGroup "SC VMM SRV01 Resources" -Value "Domain Controllers" -Keep Set-AntiAffinityClassNames -Cluster cluster01.domain.local -ClusterGroup "SC VMM SRV01 Resources" -Value "Domain Controllers" -Verbose # Example usage of the Remove-AntiAffinityClassNames script cmdlet Remove-AntiAffinityClassNames -Cluster cluster01.domain.local -ClusterGroup "SC VMM SRV01 Resources" -Clear Remove-AntiAffinityClassNames -Cluster cluster01.domain.local -ClusterGroup "SC VMM SRV01 Resources" -Value "Domain Controllers" Remove-AntiAffinityClassNames -Cluster cluster01.domain.local -ClusterGroup "SC VMM SRV01 Resources" -Value "Domain Controllers" -Verbose
Resources
Failover behavior on clusters of three or more nodes
Understanding Hyper-V Virtual Machine (VM) Failover Policies
Private Cloud: Configuring anti-affinity between VMs on Hyper-V 2008 R2 SP1 and VMM 2012
How to automatically avoid running Exchange VMs on the same Hyper-V server after a host failure
Get started with the Failover Clustering PowerShell-module
Enable Persistent Mode for Cluster Resource Groups using the PowerShell Failover Clustering module
Getting information about Run As Accounts for services and scheduled tasks
On a default Windows installation there is a number of Windows services and scheduled tasks. These are configured to run with a specified set of credentials like LocalSystem, LocalService or NetworkService. New services is typically created when installing applications, which in a domain environment often require a domain account configured as the run as account. The same requirement is true for scheduled tasks, in example a scheduled script which requires specific credentials.
A good practice is keeping track of these run as accounts, as they could potentially be a security threat. For instance a bad practice would be to configure a service or scheduled task to run as an Administrator account, when there is no need for it to run with administrative privileges.
In order to gather information about run as accounts for services and scheduled tasks I`ve created an advanced function in Windows PowerShell, named Get-RunAsAccount. The function uses Windows Management Instrumentation (WMI) to gather information about services, and schtasks.exe to gather information about scheduled tasks.
The Get-ScheduledTask function, which is a wrapper function around schtasks.exe, is created by PowerShell MVP Claus Nielsen. A two part series about managing scheduled tasks is available on the PowerShell Magazine:
- Managing scheduled tasks in your environment – Part I
- Managing scheduled tasks in your environment – Part II
The article series also provides a function for changing the username and password for a scheduled task.
Get-RunAsAccount
More than one item may be specified on the –Computername parameter, for example “Computer-A, computer-B”, thus we use a foreach construct to loop through to the $computername variable.
PROCESS { foreach ($computer in $computername) { Get-RunAsAccountWorker -computername $computer } }
Next, we set up basic error handling by using a try/catch block, where we start by sending a ping request using Test-Connection, and attempting a connection to the target computer using WMI.
try { if ((Test-Connection -ComputerName $computername -Count 1 -Quiet) -and ($computersystem = Get-WmiObject -Class win32_computersystem -Computername $computername -ErrorAction stop)) { Write-Verbose "Connected to computer $computername" $connectivity = "Success" $output = @()
If the Test-Connection cmdlet fails, the “else” script block on line 116 will be executed, populating a hash-table for the output variable where the Connectivity property is set to “Failed (ping)”. If the WMI connection fails, an exception is generated and the code in the catch block on line 132 is executed. If the connection succeed we write a message which will be shown if the –Verbose parameter is specified, informing the user that we successfully connected to the computer.
We populate the $connectivity variable which will be used later on, and we set up an empty array for the matching services and scheduled tasks found on the target computer.
Next we gather all services on the computer. Notice that we specify a filter for Get-WmiObject, which performs a wildcard match using the provided RunAsUser parameter value. If the parameter isn`t specified, all services will be returned.
$services = Get-WmiObject win32_service -filter "(StartName Like '%$runasuser%')" -ComputerName $computername -ErrorAction Stop | Select-Object name,startname if ($services) { foreach ($service in $services) { Write-Verbose "Processing service $($service.name)" $outputservice = @{} $outputservice.Computername = $computersystem.name $outputservice.Connectivity = "Success" $outputservice.Type = "Service" $outputservice.Name = $service.name $outputservice.RunAsAccount = $service.startname $output += $outputservice } }
If any services is found, a foreach construct will loop through all the services found, generate a hash-table with information about the service and add it to the $output variable created earlier. The same procedure is then repeated for gathering information about scheduled tasks, using Claus` Get-ScheduledTask function. One difference is that we first check if the RunAsUser parameter is specified, and then run the Get-ScheduledTask function with the correct parameters:
if ($RunAsUser) { $tasks = Get-ScheduledTask -ComputerName $computername -RunAsUser $RunAsUser | Select-Object taskname,runasuser } else { $tasks = Get-ScheduledTask -ComputerName $computername | Select-Object taskname,runasuser } if ($tasks) { foreach ($task in $tasks) { Write-Verbose "Processing task $($task.taskname)" $outputtask = @{} $outputtask.Computername = $computersystem.name $outputtask.Connectivity = "Success" $outputtask.Type = "ScheduledTask" $outputtask.Name = $task.taskname $outputtask.RunAsAccount = $task.runasuser $output += $outputtask } }
In the end we output the gathered information. We first check if there is any information populated in the $output variable, as this might be empty if the RunAsAccount parameter is specified, and no services or scheduled tasks running as the specified account is found. If any information is found, we do a foreach loop to output each entry (a hash-table containing either service or task information) to the pipeline:
if ($output) { foreach ($ht in $output) { New-Object -TypeName PSObject -Property $ht } } else { $outputinfo = @{} $outputinfo.Computername = $($computername) $outputinfo.Connectivity = "Success" $outputinfo.Type = $null $outputinfo.Name = $null $outputinfo.RunAsAccount = $null New-Object -TypeName PSObject -Property $outputinfo }
If the $output variable is empty we create a single object which indicates that we have successfully connected to the computer, but not found any matching services or scheduled tasks.
As a general recommendation in PowerShell; Always output the information as objects. This leaves it up to the consumer of the script or function to decide what to do next. For example he or she might decide to insert the information to a SQL-table, generate an HTML report or export the information to a CSV-file.
We will end the walkthrough by looking at some example usage:
We start by dot-sourcing the function into our current PowerShell session. There is a number of ways to define the function, have a look at the bottom of this article for more information.
Next we run the function without specifying a computer name, which means it will run on the local computer:
In the example above we specified “demo” as the value for the RunAsUser parameter. In this case “demo” is the name of the Active Directory domain, and thus all services and scheduled tasks running under a domain account will be returned.
In the next example the input for the computername parameter is gathered from a text-file, and the RunAsUser we`re looking for is “administrator”:
In the last example we`re retrieving all computer accounts in Active Directory which has the word “server” as part of the OperatingSystem property. We`re then “renaming” the name property of the computer accounts to “computername” using Select-Object, which in turn will make Get-RunAsAccount able to bind to the computername parameter when we pipe all the computer account objects to it:
We could also choose to export the information to a comma separated file:
Which in turn can be opened and customized in Microsoft Office Excel:
You can download the function from here. Feel free to leave a comment if you encounter a bug or have suggestions for improvement.
Demos, slides and videos from Nordic Infrastructure Conference 2012 sessions
On January 13th – 14th the Nordic Infrastructure Conference (NIC) was arranged in Oslo, Norway for the first time. The goal for the conference is creating a premier event for all IT-professionals in the Nordics, and the first edition of NIC was a great success with a number of excellent speakers.
Demos, slides and videos from my 3 sessions at the NIC conference is now available:
Session |
Description |
Slides |
Demos |
Video |
Introducing PowerShell 3.0 |
Since Windows 7 and Windows Server 2008 R2, Windows PowerShell has been a part of the core operating system, meaning we will see the next version of PowerShell in Windows 8. In this session we will look at what`s new in Windows PowerShell 3.0, based on the Windows Developer Preview released at the BUILD conference in September. You will get to see new features in PowerShell itself, as well as new modules for managing Windows 8 and Windows Server 8. |
|||
Practical PowerShell for the Windows Administrator |
Have you ever needed to perform the same task on more than one server? In this session you will see demos on how you can manage Windows systems using Windows PowerShell. You will get to see different techniques for remote management, demos of managing Hyper-V with and without System Center Virtual Machine Manager as well as several other demos. |
|||
Preparing for Office 365 |
In this session we will look at how you can prepare your organization for Office 365, ranging from technical requirements for clients and servers to identity management. The session will focus on the core infrastructure of the Office 365 service. |
Other PowerShell-related sessions at the conference was An introduction to WMI And PowerShell and WMI Eventing with PowerShell by PowerShell MVP Thomas Lee.
All the other sessions from the conference is available for viewing here.
2012 Microsoft MVP Award
On January 1st last year I was awarded the Microsoft MVP Award for my contributions in the PowerShell community. The MVP Award is valid for 1 year, and is based on the contributions for the past year. On January 1st this year I was re-awarded for my 2nd year as an MVP:
Dear Jan Egil Ring,
Congratulations! We are pleased to present you with the 2012 Microsoft® MVP Award! This award is given to exceptional technical community leaders who actively share their high quality, real world expertise with others. We appreciate your outstanding contributions in PowerShell technical communities during the past year.
I feel very honored to be part of this outstanding community, and I look forward to an exciting year coming with a new release of Windows and PowerShell.
Resources
Introducing the PowerShell Network Adapter Configuration module
For a Windows administrator, working with network adapters is a common task. A common approach many companies use is configuring servers with static IP addresses (IPv4) and clients using DHCP. The IP address is just one of many properties of a network adapter, as we also have properties like subnet mask, default gateway, DNS server search order and so on. Configuring these properties is usually performed during the initial setup of a computer, either manually or by a deployment mechanism.
The configuration of a network adapter is mostly static, however, from time to time some properties might need to be changed. In example the subnet on a network is expanded to meet new requirements, and thus the subnet mask property must be updated on computers not using DHCP. Another scenario is the setup of new DNS servers, with a decision not to keep the existing DNS servers` IP addresses. I recently faced the last scenario, where several hundred servers configured with static IP addresses needed to have their DNS configuration updated. Using Windows PowerShell the task can be automated, even if we need to perform the task on 10, 100 or 1000 servers. However, the more computers a script is run against, the more important it is to implement proper error handling and logging to keep track of which computers is updated.
The above task is just one of many potential tasks for configuring a network adapter. Since I was unable to find any existing PowerShell module/library for working with network adapters on Windows systems, I decided to create a project on Codeplex:
The PowerShell Network Adapter Configuration module
The PowerShell Network Adapter Configuration (PSNetAdapterConfig) module is a PowerShell module for working with network adapters on Windows-based computers. The module is using Windows Management Instrumentation (WMI) to retrieve and configure properties of network adapters.
The current release of the module makes use of the following WMI-classes:
The module currently contains 4 functions:
- Get-DNSServerSearchOrder – get the DNS client settings for a network adapter (the property name is DNSServerSearchOrder)
- Get-NetConnectionId – get the name of a network adapter (the property name is NetConnectionId)
- Set-DNSServerSearchOrder – change the configured DNSServerSearchOrder. This function contains two parameter sets, one for replacing the current DNSServerSearchOrder property value, the other for adding/removing IP addresses from the current value
- Set-NetConnectionId – change the name of a network adapter. In example, change the name from “Local Area Connection” to “Server VLAN 42”
I will add functions to the module when I find time, but I also hope that people in the community will contribute by either improving the existing functions, providing suggestions or creating new functions.
When downloading a PowerShell module from the internet (typically a zip-file), beginners often get into problems because they don`t know that the file first needs to be unblocked (Right-click the zip-file->Properties->Unblock). To overcome this challenge I created a Windows installer (MSI) file which is easy to install and does not need to be unblocked:
The default install path is %userprofile%\Documents\WindowsPowerShell\Modules, and the user have the option to override the module path.
The installer was created using Windows XML Installer (Wix), based on Chad Miller`s blog-post Building A PowerShell Module Installer.
When the module is installed, we can use Import-Module to import the module, and Get-Command to list the available commands:
As we can see there is also an alias for each of the commands (functions).
By using Get-Help we can see available parameters along with their descriptions, as well as several examples:
The functions supports both ValueFromPipeline and ValueFromPipelineByPropertyName, which means we can leverage the PowerShell pipeline. Have a look at example 3 on the above image to see how computers can be retrieved from Active Directory using Get-ADComputer, and then piped directly into the Set-DNSServerSearchOrder function.
The functions outputs PowerShell objects, which mean we can use regular PowerShell techniques to work with the data, in example converting the objects to HTML or CSV. I typically use Export-Csv to output the data to CSV-files which in turn can be converted to Excel spreadsheets:
The functions is using a try/catch construct, using the following logic:
- Ping the computer, if unsuccessful the property “Connectivity” on the output object is set to “Failed (ping”)
- Connect to the computer using WMI, if unsuccessful the property “Connectivity” on the output object is set to “Failed (ping”)
- The property “Connectivity” on the output object is set to “Successful” if step 1 and 2 succeeds
- Retrieve or change the applicable properties using WMI
When working with a large number of computers we can use Sort-Object in PowerShell to filter the output objects based on the Connectivity property, or the sorting functionality in Microsoft Excel if the output objects is piped to a CSV-file.
The final example I will show is using the Set-NetConnectionId function. On a test-computer we have a network adapter named “Local Area Connection 5”:
Using Set-ConnectionId we can specify the subnet for which network adapter to perform the change on (the above network adapter has an IP address of 192.168.56.10) and specify the new name of the network connection:
When the command has run we can see that the connection name for the adapter has changed:
One additional feature that might be included to this function is the ability to provide a hash-table with a mapping of subnets and NetConnectionIds, which will make it easy to create a consistent naming convention for network adapters.
As a final note, all functions has a Logfile parameter, allowing us to log error message to the specified file.
Requirements
- Windows PowerShell 2.0
- ICMP (ping) and WMI firewall openings from the computer running the module to the target computers
- Administrator permissions on the target computers
To avoid the firewall requirements, a workaround is running the functions from a PowerShell script locally on target computers using a software distribution product like System Center Configuration Manager.
Another option is to run the functions over PowerShell remoting.
Download and feedback
The module is available for download on the Codeplex project site http://psnetadapterconfig.codeplex.com. Please use the Discussions and Issue Tracker sites on the project site to report bugs and feature requests.
Resources
The functions is too long to walkthrough in a blog-post, so I will direct you to the following resources to get started working with advanced functions and modules in PowerShell:
An Introduction to PowerShell Modules by Jonathan Medd.
An Example ScriptModule and Advanced Function by Don Jones.
Ten tips for better PowerShell functions by James O`Neill.
Maximize the reuse of your PowerShell by James O`Neill.