Managing Hyper-V 3.0 Host and VM´s with powershell

So now i have started digging into the powershell cmdlets that are available for the Hyper-V in Windows 8 Server beta. As you can see on my screendump and also on this link there are some, i would love to see some extensions when the product becomes RTM, I am spoiled with the functionality and extensibility of the PowerCLI 😛

One thing that i like is that with powershell it is easy to actually find out what NIC you want to use in your virtual switches, why Microsoft insists of showing the description instead of name in the Hyper-V virtual switch admin gui? i have no idea which nic is which,

I do have a good name for them in the Network Connections

in the wonderful world of powershell it is now easy to create a virtual switch and actually get the right nic, also with the parameter -AllowManagementOS i make sure that my Host will not share this nic.

New-VMswitch -Name Intern -NetAdapterName VMSwitch -AllowManagementOS $false

Then i can deploy my VMs, I am woundering why the virtualization team is using bytes in their parameters ? -MemoryStartupBytes -NewVHDSizeBytes, fortunately you can use -MemoryStartupBytes 512MB or 1GB and powershell calculates it to bytes. Another thing that I am wondering about is that when i create a VM i cannot set number of vCPU, i have to change that after the VM has been created?!

New-VM -Name vNiklasDC -NewVHDSizeBytes 20GB -SwitchName Intern -MemoryStartupBytes 512MB -NewVHDPath C:\VMs\vNiklasDC\vNiklasDC.vhdx
Set-VM -Name vNiklasDC -ProcessorCount 2 -DynamicMemory -MemoryStartupBytes 512MB -MemoryMaximumBytes 1GB

More updates will come as i discover more about the new Windows 8 Server beta..

 

Enable Hyper-V 3.0 with Powershell in Windows 8 Server beta

How do you enable Hyper-V with powershell in the new win 8 Server beta, as you can see when running the Get-WindowsFeature Hyper-V i only see the Hyper-V role, but i also want the administration tools, best practice says that you should run the Hyper-V server as clean as possible and then you might want to enable the management tools on the administration server, but now i only have one server and want to be able to create VMs and configure etc.. In the screenshot i had already enabled the Hyper-V role.

To enable all Hyper-V Role/features i just type like this,

Get-WindowsFeature Hyper-V* | Add-WindowsFeature

I will test some Hyper-V powershell cmdlets after this and that might result in another post 😛

Updated VMware vSphere FT (Fault Tolerance) function for PowerCLI

After this week when i have been co-teaching a VMware vSphere ICM course i was going to show the students FT and I was seeking in the powerCLI reference for some cmdlets but could not find any.

I created my own function for enabling this, i got inspiration from Cody Bunch post but extended it with a function and also with my function you can disable/enable/remove and test restart/failover the secondary vm. I also check if the FT is enabled or not before making any changes.

Here is the PowerCLI function, i am using some Get-View to be able to use the FT functions.

Updated: Today (Monday 27/2) i added functionality with a possibility to also send VMHost as argument, but then i realized some errors occuring, my assumtions that the primary VM always was the first object when doing a Get-VM was false, i have corrected the function that it actually checks for the correct VM, Primary or Secondary. If you do not send VMHost parameter the FT process will choose a Host for you, this cannot be the same as the Primary VM resides on.

function Set-VMFaultTolerance{
<#
.SYNOPSIS
Enable FT for the VM sent as parameter

.DESCRIPTION
Use this function to enable or disable Fault Tolerance

.PARAMETER  xyz 

.NOTES
Author: Niklas Akerlund / RTS
Date: 2012-02-27
#>
    param (
   [Parameter(Position=0,Mandatory=$true,HelpMessage="A virtual machine",
    ValueFromPipeline=$True)]
    $VM,
	$VMHost = $null,
	[switch]$enableFT,
	[switch]$disableSecondaryFT,
	[switch]$removeFT,
	[switch]$failOverFT,
	[switch]$restartSecondaryFT
    )
	if ($VM.ExtensionData.Config.FtInfo -ne $null){
		$VM = Get-VM $VM | where {$_.Extensiondata.Config.FtInfo.Role -eq 1}
	} else {
		$VM = Get-VM $VM
	}
	if ($VMHost -ne $null){
		$VMHost = Get-VMHost $VMHost | Get-View
		if ($VMHost -ne $null) {
			$VMHostobj = New-Object VMware.Vim.ManagedObjectReference
			$VMHostobj.type = "HostSystem"
			$VMHostobj.value = $VMHost.MoRef.Value
			$VMHost = $VMHostobj
		}
	}

	if ($enableFT) {
		if ($VM.ExtensionData.Config.FtInfo -eq $null){
			$VMview = $VM | Get-View
			$VMview.CreateSecondaryVM($VMHost)
		} else{
			$VMsec = Get-VM $VM.Name | where {$_.Extensiondata.Config.FtInfo.Role -eq 2}| Get-View
			$VMobj = New-Object VMware.Vim.ManagedObjectReference
			$VMobj.type = "VirtualMachine"
			$VMobj.value = $VMsec.MoRef.Value
			$VMview = $VM | Get-View
			$VMview.EnableSecondaryVM($VMobj, $null)
		}
	}elseif ($disableSecondaryFT) {
		if ($VM.ExtensionData.Config.FtInfo -ne $null){
			$VMsec = Get-VM $VM.Name | where {$_.Extensiondata.Config.FtInfo.Role -eq 2 -and $_.PowerState -eq "PoweredOn"}| Get-View
			if ($VMsec -ne $null){			
				$VMobj = New-Object VMware.Vim.ManagedObjectReference
				$VMobj.type = "VirtualMachine"
				$VMobj.value = $VMsec.MoRef.Value
				$VMview = $VM | Get-View
				$VMview.DisableSecondaryVM($VMobj)
			}else {
				Write-Host "The Secondary is already disabled"
			}
		}else {
			Write-Host "This VM is not FT enabled"
		}
	}elseif ($failOverFT) {
		if ($VM.ExtensionData.Config.FtInfo -ne $null){
			$VMsec = Get-VM $VM.Name | where {$_.Extensiondata.Config.FtInfo.Role -eq 2 -and $_.PowerState -eq "PoweredOn"}| Get-View
			if ($VMsec -ne $null){
				$VMobj = New-Object VMware.Vim.ManagedObjectReference
				$VMobj.type = "VirtualMachine"
				$VMobj.value = $VMsec.MoRef.Value
				$VMview = $VM | Get-View
				$VMview.MakePrimaryVM($VMobj)
			}else {
				Write-Host "The Secondary is disabled"
			}
		}else {
			Write-Host "This VM is not FT enabled"
		}
	}elseif ($restartSecondaryFT) {
		if ($VM.ExtensionData.Config.FtInfo -ne $null){
			$VMsec = Get-VM $VM.Name | where {$_.Extensiondata.Config.FtInfo.Role -eq 2 -and $_.PowerState -eq "PoweredOn"}| Get-View
			if ($VMsec -ne $null){
				$VMobj = New-Object VMware.Vim.ManagedObjectReference
				$VMobj.type = "VirtualMachine"
				$VMobj.value = $VMsec.MoRef.Value
				$VMview = $VM | Get-View
				$VMview.TerminateFaultTolerantVM($VMobj)
			}else {
				Write-Host "The Secondary is disabled"
			}
		}else {
			Write-Host "This VM is not FT enabled"
		}
	}elseif ($removeFT){
		if ($VM.ExtensionData.Config.FtInfo -ne $null){
			$VMview = Get-VM $VM | where {$_.Extensiondata.Config.FtInfo.Role -eq 1}| Get-View
			$VMview.TurnOffFaultToleranceForVM()
		} else {
			Write-Host "This VM is not FT enabled"
		}
	} 

}

 

Add my own custom properties and populate them with powershell in VMM2012

I have made a powershell script function to update information about what VLAN and IP the VM´s have. This for the IT Admin to easily see in the VMM 2012 Console. Of course the function can be customized to populate any other information available for the VM´s, maybe you want to display create date or something else that is not in the standard properties and you do not want to use the pre-made custom# .

I am using the Get-IPAddress from Ben Wilkinson, This requires that the VMM server can resolve the IP of the VM´s from a DNS.

If I do not have an DNS or the VLAN info is not stored, i can via the parameters update the properties by -IP or -VLANid.

As you can see on this screen dump, by default i only have custom#, to add my own i have to click on “Manage Custom Properties”

Here i press “Create” and add a name and optionally a description

Then when i have created the custom properties i want i add them,

Then it looks like this,

Now the powershell function comes into play when i am going to populate these fields with information.

function Set-CustomValue{
# Function to add custom data on VMs
#
# Niklas Akerlund /RTS 2012-02-18
    param (
   [Parameter(Position=0,Mandatory=$true,HelpMessage="A virtual machine pleaze",
    ValueFromPipeline=$True)]
    $VM,
	$VLANid = "",
	$IP = ""
    )
    . .\Get-IPAddress.ps1
    
    $VM = Get-SCVirtualMachine $VM
	
  	$CustomIP = Get-SCCustomProperty -Name "IP"
    $CustomVLAN = Get-SCCustomProperty -Name "VLAN"
    
	if ($IP -eq ""){
		$IP = Get-IPAddress -HostName $VM.Name
    }
	
	if ($VLANid -eq ""){
	    $VMnics = $VM | Get-SCVirtualNetworkAdapter
	    if($VMnics.Count -ne $null){
	        foreach ($VMnic in $VMnics){
	            $VLANid = $VLANid + $VMnic.VlanID + " "
	        }
	        Set-SCCustomPropertyValue -InputObject $VM -CustomProperty $CustomVLAN -Value $VLANid
	    }else{
	        Set-SCCustomPropertyValue -InputObject $VM -CustomProperty $CustomVLAN -Value $VMnics.VlanID
	    }
    }else {
		Set-SCCustomPropertyValue -InputObject $VM -CustomProperty $CustomVLAN -Value $VLANid
	}
    if($IP -ne ""){
        Set-SCCustomPropertyValue -InputObject $VM -CustomProperty $CustomIP -Value $IP       
    }
}

a screendump of my powershell console, as you can see I am using the (Get-SCVMHostCluster).Nodes to get all virtual machines on these, also i am using a foreach (%) to run the population on each VM.

And this is how it looks in the VMM console,

 

Migrate VM´s in SCVMM 2012 from one cluster to a new

I have made a simple powershell function that can move VM´s from one cluster to another and also set the network configuration, this because the old cluster had one way of setup regarding the virtual machine network and the new another more flexible with promiscuous mode set on the HP nic team so i do not need to create one virtual switch for every vlan. The function also checks if the actual VM has snapshots before moving it. (the SCVMM calls it checkpoints, which is stupid to call it one thing in hyper-v and another in SCVMM!)

the powershell function looks like this:

function Migrate-VM {

param (
   [Parameter(Position=0,Mandatory=$true,HelpMessage="A virtual machine pleaze",
    ValueFromPipeline=$True)]
    $VM,
    [Parameter(Mandatory=$true)]$VMHost,
    $VMnic,
    $VMnetwork = "VMs",
    $Datastore = "C:\ClusterStorage\Volume1",
    [switch]$StartVM
    )
    
    if( Get-SCVirtualMachine $VM | Get-SCVMCheckpoint) {
		$VM = Get-SCVirtualMachine $VM
		Write-Host " Please remove snapshots and do a shutdown of the VM:" + $VM.Name
    }else{
	    $JobGroupID = [guid]::NewGuid()
	    
	    $VMHost = Get-SCVMHost $VMHost
	    $VMnic = Get-SCVirtualMachine $VM | Get-SCVirtualNetworkAdapter
	    
	    $LogicalNetwork = (Get-SCLogicalNetworkDefinition -VLanID $VMnic.VlanID).LogicalNetwork

	    Set-SCVirtualNetworkAdapter -VirtualNetworkAdapter $VMnic -RunAsynchronously -VirtualNetwork $VMnetwork -LogicalNetwork $LogicalNetwork -VLanEnabled $true -VLanID $VMnic.VlanID -JobGroup $JobGroupID -EnableVMNetworkOptimization $false -EnableMACAddressSpoofing $false
	    if($StartVM){
	        Move-SCVirtualMachine -VM $VM -VMHost $VMHost -HighlyAvailable $true -Path $Datastore -StartVMOnTarget -RunAsynchronously -UseLAN -JobGroup $JobGroupID
	    }else
	    {
	        Move-SCVirtualMachine -VM $VM -VMHost $VMHost -HighlyAvailable $true -Path $Datastore -RunAsynchronously -UseLAN -JobGroup $JobGroupID
	    }
    }   
    
}

And this screendump shows some different ways of running it,

VM inventory function with Powershell on SCVMM 2012

Had a plan to do a blog post last week but was so busy studying for the VCP 5 test. I wrote the test on friday and i passed 🙂

I have created a little powershell function, this to get the inventory information about the virtual machines running in hosts or clusters that are connected to a System Center Virtual Machine Manager. This function collects number of configured vCPU´s, Memory, information about the connected virtual harddisks. This kind of information could be handy as in the case with one customer that has not yet implemented a host based backup solution and still use agents in their VM´s, so if they need to recover machines it is good to know what size and configuration it had if it is lost by some reason. This report can also be handy for some info on how much your datastores are filled. As you can se i both report the maximum size and the current (as dynamic disks are not allocating the whole size at creation time)

The function both export a csv and a HTML report

After importing it to excel it can look something like this:

And the html output is very simple but can be put on a internal web for IT production

I have used the Get-SCVirtualDisk to get the Bus Type and LUN id as this information is not available in Get-SCVirtualHarddisk, this information can be good if i lost the virtual machine configuration and want to connect the virtual harddisks to the right controller.

the function looks like this: (if you copy this you have to move the first # up to the <, the blog syntax highlighter does something and move it down :-( )

function Get-VMdata{
<#
.SYNOPSIS
Get the configuration data of the VMs in Hyper-V via SCVMM 2012
 
.DESCRIPTION
Use this function to get all VMs configuration in case of disaster or just statistics
 
.PARAMETER  xyz 
 
.NOTES
Author: Niklas Akerlund / RTS
Date: 2012-02-13
#>
param (
	$VMHostGroup = "All Hosts",
	[Parameter(ValueFromPipeline=$True)][Alias('ClusterName')]
	$VMHostCluster = $null,
	$VMHost = $null,
	[string]$CSVFile = "VMdata.csv",
    [string]$HTMLReport = "VMdata.html"
    )
	
	$report = @()
	if ($VMHostCluster -eq $null){
		$VMHosts = (Get-SCVMHostGroup -Name $VMhostGroup).AllChildHosts
	}else{
			$VMHosts = (Get-SCVMHostCluster -Name $VMHostCluster).Nodes
	}
	$VMs = $VMHosts | Get-SCVirtualMachine
	
	foreach ($VM in $VMs) {
		$VHDs = $VM | Get-SCVirtualDiskDrive
		$i = "1"
		foreach ($VHDconf in $VHDs){ 
			if($i -eq "1"){
				$data = New-Object PSObject -property @{
					VMName=$VM.Name
			        vCPUs=$VM.CPUCount
			        MemoryGB= $VM.Memory/1024
					VHDName = $VHDconf.VirtualHardDisk.Name
					VHDSize = $VHDconf.VirtualHardDisk.MaximumSize/1GB
					VHDCurrentSize = [Math]::Round($VHDconf.VirtualHardDisk.Size/1GB)
					VHDType = $VHDconf.VirtualHardDisk.VHDType
					VHDBusType = $VHDconf.BusType
					VHDBus = $VHDconf.Bus
					VHDLUN = $VHDconf.Lun
					VHDDatastore = $VHDconf.VirtualHardDisk.HostVolume
				}
				$i= "2"
			}else{
				$data = New-Object PSObject -property @{
					VMName=""
			        vCPUs=""
			        MemoryGB= ""
					VHDName = $VHDconf.VirtualHardDisk.Name
					VHDSize = $VHDconf.VirtualHardDisk.MaximumSize/1GB
					VHDCurrentSize = [Math]::Round($VHDconf.VirtualHardDisk.Size/1GB)
					VHDType = $VHDconf.VirtualHardDisk.VHDType
					VHDBusType = $VHDconf.BusType
					VHDBus = $VHDconf.Bus
					VHDLUN = $VHDconf.Lun
					VHDDatastore = $VHDconf.VirtualHardDisk.HostVolume
				}
			}
			$report +=$data	
		}
	}
	$report | Select-Object VMName,vCPUs,MemoryGB,VHDName,VHDSize,VHDCurrentSize,VHDType,VHDBusType,VHDBus,VHDLUN,VHDDatastore | Export-Csv -Path $CSVFile -NoTypeInformation -UseCulture
	$report | Select-Object VMName,vCPUs,MemoryGB,VHDName,VHDSize,VHDCurrentSize,VHDType,VHDBusType,VHDBus,VHDLUN,VHDDatastore | ConvertTo-HTML | Out-File $HTMLReport
}

and i can run it in some different ways, as you see in this screenshot, if i do not give any parameters it will use VMHostGroup “All Hosts” as default

SCVMM 2012 powershell function for configure network on hosts

After bare metal deployment of the hosts we need to configure the network on the hosts, this can be done from the SCVMM 2012 powershell console.

I have made a function of the creation of first a virtual switch and also adding the logical networks, if i do not add the networks to the particular host nic where the virtual switch is i cannot deploy virtual machines and get them connected to the network.

You can add the logical networks and their associated vlan/subnet before any host is imported into SCVMM and of course i have created a function for that also, this can take a csv file or just the parameters

Here is a screen dump if i run it without a csv file

and the result is the following

And here is the powershell code for the add logical networks function

function Add-SCLogicalNetworks{
<#
.SYNOPSIS
Add logical networks to your SCVMM fabric
 
.DESCRIPTION
With this function you add logical networks
 
.PARAMETER  CSVNetworks
Path to a CSV file with the logical network info 
 
.NOTES
Author: Niklas Akerlund / RTS
Date: 2012-02-02
#>
param (
    $CSVNetworks = "",
	$VMHostGroup = "All Hosts",
	[string]$Name = "",
	[string]$IPnet = "",
    	[string]$Octet = "24",
	[int]$VLAN = 0
    )
$VMHostGroup = Get-SCVMHostgroup $VMHostGroup

if ($CSVNetworks -ne "") {	
	$VLANs = Import-Csv $CSVNetworks -Delimiter ";"

	foreach ($VLAN in $VLANs){
	    $LogicalNetwork = New-SCLogicalNetwork -Name $VLAN.Name
	    $Network = $VLAN.IPnet + "/" + $VLAN.Octet
	    $SubnetVlan = New-SCSubnetVLan -Subnet $Network -VLanID $VLAN.VLAN
	    New-SCLogicalNetworkDefinition -Name $VLAN.Name -LogicalNetwork $logicalNetwork -VMHostGroup $VMHostGroup -SubnetVLan $SubnetVlan 
	}
}else{
	$LogicalNetwork = New-SCLogicalNetwork -Name $Name
	$Network = $IPnet + "/" + $Octet
	$SubnetVlan = New-SCSubnetVLan -Subnet $Network -VLanID $VLAN
	New-SCLogicalNetworkDefinition -Name $Name -LogicalNetwork $logicalNetwork -VMHostGroup $VMHostGroup -SubnetVLan $SubnetVlan 
}
}

when i have the logical networks i want to configure the host

And the result is the following

And this is also displayed on the physical nic

and here is the powershell code for configuring the host:

function Configure-SCVMhostNetwork{
<#
.SYNOPSIS
Configure the network on the Hyper-V Host
 
.DESCRIPTION
With this function you set virtual switch and configure logical networks
 
.PARAMETER  VMHost
The new host that is going to be configurered 

.PARAMETER refVMHost
if there are different hyper-v clusters and logical networks, use a ref host for the configuration to set it up
 
.PARAMETER VMHostnic
The nic that is going to be configured with a virtual switch (in this case a NIC team named "VM"
 
.EXAMPLE
PS C:\> Get-SCVMHost Hyp04 | Configure-SCVMhostNetwork

.EXAMPLE
PS C:\> Configure-SCVMhostNetwork -VMHost Hyp04 -refVMHost Hyp06
 
.NOTES
Author: Niklas Akerlund / RTS
Date: 2012-02-02
#>
param (
   [Parameter(Position=0,Mandatory=$true,HelpMessage="This need to be a Hyper-V Host name or object",
    ValueFromPipeline=$True)]
    $VMHost,
	[string]$refVMHost = "",
	[string]$VirtualNetworkName = "VMs",
    [string]$VMHostnic = "VM"
    )
 
$VMHost = Get-VMHost $VMHost
if ($VMHost -ne $null) {
	if ($refVMHost -eq ""){
		$LogicalNetworks = Get-SCLogicalNetwork
	}else
    {
		$LogicalNetworks = Get-SCLogicalNetwork -VMHost $refVMHost
	}
	
	$vmHostNetworkAdapter = Get-SCVMHostNetworkAdapter -VMHost $VMHost -Name $VMHostnic
	$virtualNetwork = New-SCVirtualNetwork -VMHost $VMHost -Name $VirtualNetworkName -Description "" -BoundToVMHost $false -VMHostNetworkAdapters $vmHostNetworkAdapter	   
	$vmHostNetworkAdapter = Get-SCVMHostNetworkAdapter -Name $VMHostnic -VMHost $VMHost
	foreach ($LogicalNet in $LogicalNetworks){
		Set-SCVMHostNetworkAdapter -VMHostNetworkAdapter $vmHostNetworkAdapter -AddOrSetLogicalNetwork $logicalNet
	}
}
}

 

Updated->Schedule PowerShell script in windows 2003 to clean any files

I have set up an scheduled task in a windows 2003 that run a PowerShell script, this script runs every week and the purpose is to clean out logs from IIS that otherwise fills the volume.

I used the script from Thomas Maurer to check a folder for files to delete and have customized it after excellent input from Jeffery Hicks, he suggested that i put in parameters instead to make the script more flexible and that way it can be run on any folder in the server with -Path and also set number of days to save -DaysBack, the last parameter has a default value of 30 days:

# Delete all Files in specifed parameter Path 
# and parameter older than 30 day(s) default or
# set by parameter DaysBack
#
# Parameter delyxe Niklas Akerlund / RTS
param(
	[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]$Path,
	$DaysBack = 30)

$CurrentDate = Get-Date
$DatetoDelete = $CurrentDate.AddDays(-$Daysback) 

if($Path -ne ""){
	Get-ChildItem $Path | Where-Object { $_.LastWriteTime -lt $DatetoDelete } | Remove-Item
}

I saved it in C:\Scripts\Clean and you must set the execution policy in PowerShell console otherwise you will not be able to run any scripts,

Set-ExecutionPolicy -ExecutionPolicy Remotesigned

then i created the scheduled task, some screen shots follows with the way to set up this, first selesct PowerShell

then select interval that the script is going to be run

I have selected that the script is going to be executed every week at mondays 9 AM

Then set an account and password and click next, the magic comes when going into the advanced properties

As you can see i have added a -File C:\Scripts\Clean\clean.ps1 -Path “C:\windows\system32\LogFiles\W3SVC1” -DaysBack 20 to the PowerShell.exe in the run field, otherwise i will not run any scripts 😉 only the powershell, the -File was also a suggestion from Jeffery Hicks 🙂

C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -File c:\Scripts\Clean\clean.ps1 -Path "C:\windows\system32\LogFiles\W3SVC1" -DaysBack 20

Quite simple but so powerful to not have to manually clean folders with logfiles that grows.

If you do not have PowerShell on your windows 2003 you have to install the Windows Management Framework first: kb968929

This script can also of course be run on xp, win7 and win 2008/2008 R2.

Populate and administer Active directory users with Powershell

Some days ago I have been working on some powershell scripts for a lab environment and now i have edited the handlescript to work better and with more functionality.

First i created a small script for populating some OU with student accounts.

# Script: Createusers.ps1
# Create users in AD
# Version: 0.1
# Niklas Akerlund /RTS

Import-Module Activedirectory

$OUs =  "OU1","OU2","OU3","OU4","OU5","OU6"

foreach ($OU in $OUs){
	$path = "OU=" + $OU + ",OU=lab,OU=Users,DC=demo,DC=local"
	for ($i=1;$i -le 10;$i++){
    	$SAMid = $OU + "Student0" + $i 
    	$Name = $OU + "Student0" + $i 
    	New-ADUser -SamAccountName $SAMid -Name $Name -AccountPassword (ConvertTo-SecureString -AsPlainText "S0meP@ssw0rd" -Force) -Enabled $true -Path $path
   
	}
}

 

Then i created with help from Jeffery Hicks post a script with a menu to enable or disable accounts in the different OU´s. As i described in a earlier post it is quite powerful to use Active Directory cmdlets and i have tried to take care of that by checking that when i enable/disable accounts i check that i really have something in my searchbase. Below are two screenshots of the script running. Update I have now updated the script with a check for the searchbase that actually works and also an extra menu option with the ability to set password on the accounts i enable!

 

Here is the script that creates this, i have not yet come up with a better and more dynamic way to create the switch, maybe someone can give me some help there..

# Script: handleusers.ps1
# Version: 0.1
# Disable or enable lab account users
# 
# Niklas Akerlund / RTS
# Menu code from Jeffery Hicks 

Import-Module ActiveDirectory
 
Function Show-Menu {

	Param(
		[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter your menu text")]
		[ValidateNotNullOrEmpty()]
		[string]$Menu,
		[Parameter(Position=1)]
		[ValidateNotNullOrEmpty()]
		[string]$Title="Menu",
		[switch]$ClearScreen
	)

	if ($ClearScreen) {Clear-Host}

	#build the menu prompt
	$menuPrompt=$title
	#add a return
	$menuprompt+="<code>n"
	#add an underline
	$menuprompt+="-"*$title.Length
	$menuprompt+="</code>n"
	#add the menu
	$menuPrompt+=$menu

	Read-Host -Prompt $menuprompt

} #end function

$menu="<code>n"
$ListOU = Get-ADOrganizationalUnit -filter * -Searchbase "OU=lab,OU=Users,DC=demo,DC=local" | where {$_.Name -ne "lab"} | Sort-Object $_.Name

$i = 1
foreach ($OU in $ListOU){
	   $menu+= [string]$i + " " + $OU.Name + "</code>n"
      # write-host $menu
       $i = $i+1
     
}
$menu+= "Q Quit"
$menu+= "<code>n"

# Menu 2 
$menu2=@"
1 Disable accounts
2 Enable accounts
3 Set password and enable accounts
B Back to menu
"@
$menu2+= "</code>n"

#Keep looping and running the menu until the user selects Q (or q).
Do {
    #use a Switch construct to take action depending on what menu choice
    #is selected.
    Switch (Show-Menu $menu "Get OU to handle" -clear ) {
     "1" {
	 		if ($ListOU[0].DistinguishedName -ne $null){
		 		$menutext = "Handle " + $ListOU[0].Name + " users to disable/enable "
				$Users = Get-ADUser -filter * -searchbase $ListOU[0].DistinguishedName
		 		Switch (Show-Menu $menu2 $menutext -clear ) {
	     		"1" {
		 					$Users |  Set-ADUser -Enabled $false	
	         		} 
	     		"2" {
		 					$Users |  Set-ADUser -Enabled $true	
	          		}
	     		"3" {
							$newPassword = (Read-Host -Prompt "Provide New Password" -AsSecureString)
		 					$Users | Set-ADUser -Enabled $true	
							$Users | Set-ADAccountPassword -NewPassword $newPassword -Reset
	          		}
				"B" {
						Write-Host "Back to menu" -ForegroundColor Cyan
	        		}
				Default {Write-Warning "Invalid Choice. Try again."
	              sleep -milliseconds 750}
	         	}
			}
		 }
     "2"  {
	 		if ($ListOU[1].DistinguishedName -ne $null){
		 		$menutext = "Handle " + $ListOU[1].Name + " users to disable/enable " 
				$Users = Get-ADUser -filter * -searchbase $ListOU[1].DistinguishedName
		 		Switch (Show-Menu $menu2 $menutext -clear ) {
	     		"1" {
		 				
		 					$Users |  Set-ADUser -Enabled $false	
						
	         		} 
	     		"2" {
		 					$Users |  Set-ADUser -Enabled $true	
	          		}
				"3" {
							$newPassword = (Read-Host -Prompt "Provide New Password" -AsSecureString)
		 					$Users | Set-ADUser -Enabled $true	
							$Users | Set-ADAccountPassword -NewPassword $newPassword -Reset
	          		}
				"B" {
						Write-Host "Back to menu" -ForegroundColor Cyan
	        		}
				Default {Write-Warning "Invalid Choice. Try again."
	              sleep -milliseconds 750}
	         	} 
			}
		 }
     "3" {
	 		if ($ListOU[2].DistinguishedName -ne $null){
		 		$menutext = "Handle " + $ListOU[2].Name + " users to disable/enable " 
				$Users = Get-ADUser -filter * -searchbase $ListOU[2].DistinguishedName
		 		Switch (Show-Menu $menu2 $menutext -clear ) {
	     		"1" {
		 					$Users |  Set-ADUser -Enabled $false		
	         		} 
	     		"2" {
		 					$Users |  Set-ADUser -Enabled $true	
	          		}
				"3" {
							$newPassword = (Read-Host -Prompt "Provide New Password" -AsSecureString)
		 					$Users | Set-ADUser -Enabled $true	
							$Users | Set-ADAccountPassword -NewPassword $newPassword -Reset
	          		}
				"B" {
						Write-Host "Back to menu" -ForegroundColor Cyan
	        		}
				Default {Write-Warning "Invalid Choice. Try again."
	              sleep -milliseconds 750}
	         	} 
			}
		 }
	 "4" {
	 		if ($ListOU[3].DistinguishedName -ne $null){
		 		$menutext = "Handle " + $ListOU[3].Name + " users to disable/enable " 
				$Users = Get-ADUser -filter * -searchbase $ListOU[3].DistinguishedName
		 		Switch (Show-Menu $menu2 $menutext -clear ) {
	     		"1" {
		 					$Users |  Set-ADUser -Enabled $false		
	         		} 
	     		"2" {
		 					$Users |  Set-ADUser -Enabled $true	
	          		}
				"3" {
							$newPassword = (Read-Host -Prompt "Provide New Password" -AsSecureString)
		 					$Users | Set-ADUser -Enabled $true	
							$Users | Set-ADAccountPassword -NewPassword $newPassword -Reset
	          		}
				"B" {
						Write-Host "Back to menu" -ForegroundColor Cyan
	        		}
				Default {Write-Warning "Invalid Choice. Try again."
	              sleep -milliseconds 750}
	         	} 
			}
		 }
	 "5" {
	 		if ($ListOU[4].DistinguishedName -ne $null){
		 		$menutext = "Handle " + $ListOU[4].Name + " users to disable/enable " 
				$Users = Get-ADUser -filter * -searchbase $ListOU[4].DistinguishedName
		 		Switch (Show-Menu $menu2 $menutext -clear ) {
	     		"1" {
		 					$Users |  Set-ADUser -Enabled $false		
	         		} 
	     		"2" {
		 					$Users |  Set-ADUser -Enabled $true	
	          		}
				"3" {
							$newPassword = (Read-Host -Prompt "Provide New Password" -AsSecureString)
		 					$Users | Set-ADUser -Enabled $true	
							$Users | Set-ADAccountPassword -NewPassword $newPassword -Reset
	          		}
				"B" {
						Write-Host "Back to menu" -ForegroundColor Cyan
	        		}
				Default {Write-Warning "Invalid Choice. Try again."
	              sleep -milliseconds 750}
	         	} 
			}
		 }
	 "6" {
	 		if ($ListOU[5].DistinguishedName -ne $null){
		 		$menutext = "Handle " + $ListOU[5].Name + " users to disable/enable " 
				$Users = Get-ADUser -filter * -searchbase $ListOU[5].DistinguishedNam
		 		Switch (Show-Menu $menu2 $menutext -clear ) {
	     		"1" {
		 					$Users |  Set-ADUser -Enabled $false	
						
	         		} 
	     		"2" {
		 					$Users |  Set-ADUser -Enabled $true	
	          		}
				"3" {
							$newPassword = (Read-Host -Prompt "Provide New Password" -AsSecureString)
		 					$Users | Set-ADUser -Enabled $true	
							$Users | Set-ADAccountPassword -NewPassword $newPassword -Reset
					}          	
				"B" {
						Write-Host "Back to menu" -ForegroundColor Cyan
	        		}
				Default {Write-Warning "Invalid Choice. Try again."
	              sleep -milliseconds 750}
	         	} 
			}
		 }
     "Q" {Write-Host "Goodbye" -ForegroundColor Cyan
         Return
         }
     Default {Write-Warning "Invalid Choice. Try again."
              sleep -milliseconds 750}
    } #switch
} While ($True)