Archive | PowerShell

Powershell script to install Cygwin

I like having Cygwin installed on my machine, and since I always re-image, I needed a script to install Cygwin automatically.

function Install-Cygwin {
   param ( $TempCygDir="$env:temp\cygInstall" )
   if(!(Test-Path -Path $TempCygDir -PathType Container))
    {
       $null = New-Item -Type Directory -Path $TempCygDir -Force
    }
   $client = new-object System.Net.WebClient
   $client.DownloadFile("http://cygwin.com/setup.exe", "$TempCygDir\setup.exe" )
   Start-Process -wait -FilePath "$TempCygDir\setup.exe" -ArgumentList "-q -n -l $TempCygDir -s http://mirror.nyi.net/cygwin/ -R c:\Cygwin"
   Start-Process -wait -FilePath "$TempCygDir\setup.exe" -ArgumentList "-q -n -l $TempCygDir -s http://mirror.nyi.net/cygwin/ -R c:\Cygwin -P openssh"
}

This will download and install Cygwin and install the openssh package.

PowerShell to list all users and when their password expires

I wanted to dump a list of accounts and their password expiration dates – accounts that were not disabled, that had a certain description, and were not set with “Password never expires”

(Get-ADUser -filter {(Description -notlike "Service*") -and (Enabled -eq "True") -and (PasswordNeverExpires -eq "False")} -properties *) |
select samaccountname,description,
@{N="LastChanged";E={(Get-Date([System.DateTime]::FromFileTimeUtc($_.pwdLastSet))).ToShortDateString()}},
@{N="Expires";E={(Get-Date([System.DateTime]::FromFileTimeUtc($_.pwdLastSet))).AddDays((Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.TotalDays).ToShortDateString()}}

My PowerShell cheat sheet

I am trying to  be a better PowerSheller. I thought I would create a post with queries I have figured out. I hope to keep adding more.

  • We needed to change a whole bunch of distribution groups – append them with “-NewName”. This query created our origianl list, the last 50 distribution groups created, with columns representing the old and new names:
Get-ADGroup -filter {GroupCategory -eq "Distribution"} -Properties *|
Select-Object -Property Name,whenCreated,mail,
@{N="NewMail";E={$_.mail.replace("@","-NewName@")}} -last 50
  • Similar to the query above, but adds a column indicating if we had changed the name or not – a status column. I used a conditional inside of an Expression field. And I looped through an array retuned by “proxyAddresses”
Get-ADGroup -filter {GroupCategory -eq "Distribution"} -Properties * |
sort created |
select Name,
@{N="NewName";E={if($_.Name -like "*-NewName"){$_.Name}else{$_.Name+"-NewName"}}},
@{N="NewMail";E={if($_.Mail -like "*-NewName@domain.com"){$_.Name}else{$_.Mail.replace("@","-NewName@")}}},
@{Name="proxyAddresses";Expression={foreach ($SMTP in $_.proxyAddresses){if ($SMTP.ToLower().StartsWith("smtp:")){$SMTP}}}},
@{N="Completed";E={if($_.Name -like "*-NewName"){"Completed"}else{"ToDo"}}} |
convertto-csv > MasterList.csv
  • Another variation with a conditional inside of  the filter
Get-ADGroup -filter {GroupCategory -eq "Distribution" -and Name -like "*-NewName"} -Properties * |
sort created | select Name,Samaccountname,Displayname,Mailnickname,Mail,
@{Name="proxyAddresses";Expression={foreach ($SMTP in $_.proxyAddresses){if ($SMTP.ToLower().StartsWith("smtp:")){$SMTP}}}} |
convertto-csv > Completed.csv
  • More to come. Maybe these will be helpful to some searchers out there.

How to update ESXi 4.1 without vCenter

I wanted to update a standalone ESXi box from 4.1 to 4.1 Update 1. Here is how I went about it:

  1. Downloaded the update on a windwos box from here and unziped it
  2. Open the viClient datastore browser and upload the unzipped folder. If you put it off the root of your datastore, the path will be:
    1. /vmfs/volumes/datastore1/update/update-from-esxi4.1-4.1_update01
  3. Install the VMware vSphere PowerCLI – which is a Windows PowerShell interface to the vSphere API
  4. Add the VMware cmdlts to your PowerShell session: add-pssnapin “VMware.VimAutomation.Core”
  5. Put the ESXi server into maintenance mode.
  6. In PowerShell, connect to the ESXi server:  Connect-VIServer servername.domain.local
  7. In PowerShell: Install-VMHostPatch -HostPath /vmfs/volumes/datastore1/update-from-esxi4.1-4.1_update01/metadata.zip
  8. The result was: WARNING: The update completed successfully, but the system needs to be rebooted for the changes to be effective.
  9. Reboot!

The summary below was also returned:

Id                                              VMHostId IsIns IsApp Needs Needs
                                                         talle licab Resta Recon
                                                         d     le    rt    nect
--                                              -------- ----- ----- ----- -----
cross_oem-vmware-esx-drivers-scsi-3w-9xxx_... ...ha-host False True  True  False
cross_oem-vmware-esx-drivers-net-vxge_400.... ...ha-host False True  True  False
deb_vmware-esx-firmware_4.1.0-1.4.348481      ...ha-host False False True  False
deb_vmware-esx-tools-light_4.1.0-1.4.348481   ...ha-host True  True  False False

My PowerShell PowerCLI VMware guest provisioning script

This script will provision a 4GB Ram, 40 GB HD Server 2008 R2 VM, set the CD to an OSD iso, set the BootDelay to 5 seconds, and start the machine


$vmhost = Get-VMHost "server.name.local"
$ds = Get-Datastore "server:storage1"
$rp = get-resourcepool -id "ResourcePool-resgroup-22"
$nn = "NetworkName"
$gi = "windows7Server64Guest"
$iso = "[server:ISOs] Folder/OSD.iso"

####
$vmname = "VMGeust01"
New-VM -name $vmname -VMHost $vmhost -numcpu 1 -DiskMB 40960 -memoryMB 4096 -datastore $ds -guestID $gi -resourcepool $rp -cd -NetworkName $nn
Get-VM $vmname | Get-CDDrive | Set-CDDrive -IsoPath $iso -StartConnected $true -Confirm:$false

$value = "5000"
$vm = Get-VM $vmname | Get-View
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
$vmConfigSpec.BootOptions = New-Object VMware.Vim.VirtualMachineBootOptions
$vmConfigSpec.BootOptions.BootDelay = $value
$vm.ReconfigVM_Task($vmConfigSpec)

Start-VM -VM $vmname

Exchange 2010 SP1 and New-DatabaseAvailabilityGroup

I was progressing along on with my offline Exchange install, and I ran into a problem when creating my Database Availability Group (DAG). I wanted to put the witness directory on a non exchange server and the instructions say:

If the witness server you specify isn’t an Exchange 2010 server, you must add the Exchange Trusted Subsystem universal security group to the local Administrators group on the witness server.

I added the correct group to the correct group and I run:

New-DatabaseAvailabilityGroup DAGNAME -witnessserver nonexchange.domain.local -witnessdirectry c:\DAGFSW

I received the following error:

WARNING: The Exchange Trusted Subsystem is not a member of the local Administrators group on specified witness server nonexchange.domain.local.

But it is in there, believe me I tripple checked. I also tried:

  • I rebooted the server, deleted the DAG and tried again. Nada.
  • I added the witness machine$ account to the local Administrator’s account – since all it is doing is creating a shared directory. Nope.
  • Looked on the witness server, and did not see a shared folder.
  • But, I never added a member to the DAG because I thought that the shared folder should be there.

So I started reading and I came across this article. Devin suggests that all you need to do, like the documentation says, is add the Exchange Trusted Subsystem to the local administrators group, and NOT add the witness machine$ account to the Exchange Trusted Subsystem group. I agree with his argument as to why it is not necessary.

BUT. I think there might be a bug in SP1. My findings are:

  • If you run the New-DatabaseAvailabilityGroup command and ONLY have the Exchange Trusted Subsystem as a member of the witness’s local Administrators group:
    1. You will receive this error: WARNING: The Exchange Trusted Subsystem is not a member of the local Administrators group on specified witness server nonexchange.domain.local.
    2. If your witness folder is a directory or two deep, parent directories will be created
    3. The witness shared folder will not be created until you add a member to the DAG
  • If you run the New-DatabaseAvailabilityGroup command AND have the witness machine$ account in the Exchange Trusted Subsystem:
    1. You will NOT receive an error.
    2. The witness shared folder will not be created until you add a member to the DAG

So, in summary, it seems:

  • That there is a bug in SP1 in the New-DatabaseAvailabilityGroup command. It incorrectly reports that “The Exchange Trusted Subsystem is not a member of the local Administrators group”, when it is.
  • New-DatabaseAvailabilityGroup creates the DAG and even though it spits back an error, everything seems to function once a DAG member has been created – the witness folder is created
  • Devin’s article is still a valid recommendation as you do not need to add the non exchange witness machine$ account to the Exchange Trusted Subsystem group to get a DAG up and running.

Of course there could be other things at play, but as of now, this is what I have found.

A second addition to my PowerShell install script

I added even more functionality in my PowerShell install script (original script, and first update). I wanted the ability to display an informational popup to let the user know what we were up to. The function below takes the text to display from the config xml file and displays it in a windows form, with an “OK” button.

This script is starting to be fun!


Function DisplayWindowsForm ($Step){
	if ($Step.TextToDisplay.Value -ne "") {
    	$TextToDisplay = $Step.TextToDisplay.Value
		[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
		[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 
		
		$objForm = New-Object System.Windows.Forms.Form 
		$objForm.Text = "Title bar"
		$objForm.Size = New-Object System.Drawing.Size(600,500) 
		$objForm.StartPosition = "CenterScreen"
		
		$OKButton = New-Object System.Windows.Forms.Button
		$OKButton.Location = New-Object System.Drawing.Size(250,400)
		$OKButton.Size = New-Object System.Drawing.Size(75,23)
		$OKButton.Text = "OK"
		$OKButton.Add_Click({$x=$objTextBox.Text;$objForm.Close()})
		$objForm.Controls.Add($OKButton)
		
		$objLabel = New-Object System.Windows.Forms.Label
		$objLabel.Location = New-Object System.Drawing.Size(10,20) 
		$objLabel.Size = New-Object System.Drawing.Size(500,400) 
		$objLabel.Text = $TextToDisplay
		$objForm.Controls.Add($objLabel) 
		
		$objForm.Topmost = $True
		
		$objForm.Add_Shown({$objForm.Activate()})
		[void] $objForm.ShowDialog()
	}
}

An update to my new PowerShell install script

I needed new functionality in my PowerShell install script (previously mentioned here). I needed the ability to make sure a process is not running, and if it is running, I could prompt the user to close it.

The new function takes the process name value from the config xml file (as I mentioned in previos post) and if it is running, displays a message box until the program is no longer running.


Function WaitForProcessToStop ($Step){
	$ProcessName= ($Step.ProcessName.Value).split(".")[0]
    if ($ProcessName -ne "") {
		[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
		While (get-process $ProcessName -ea SilentlyContinue | select -Property Responding)
		{
			[System.Windows.Forms.MessageBox]::Show(
			"Please close $ProcessName", 
			"Please close $ProcessName", 
			[System.Windows.Forms.MessageBoxButtons]::OK, 
			[System.Windows.Forms.MessageBoxIcon]::Information,
			[System.Windows.Forms.MessageBoxDefaultButton]::Button1,   
			[System.Windows.Forms.MessageBoxOptions]::ServiceNotification
			)
		}
	}
}

My new PowerShell install script

I wanted to write a PowerShell script that can execute common activities involved in deploying software. We require signed PowerShell scripts, so it was not practical to rewrite the script for every piece of software. Instead, I moved the configuration to an XML file.

The first function below takes an object (pulled from the XML config file) that contains the uninstall information. The scenario would be that you want to uninstall a piece of software before you install something else. First I try to remove the software via WMI. If that fails, I lookup the uninstall string and use msiexe.exe to try and uninstall the software bassed on the GUID of the software.

function UninstallStep ($Step)
{
    $CurrentDisplayName= $Step.CurrentDisplayName.Value
    $CurrentVersion= $Step.CurrentVersion.Value
      gwmi win32_product -filter "Name like '%$CurrentDisplayName%'" | foreach {
        $InstalledVersion = $_.Version
        if ($InstalledVersion -ne  $CurrentVersion) {
            write-host "Trying to uninstall $CurrentDisplayName $InstalledVersion via WMI"
	        if ($_.uninstall().returnvalue -eq 0) { write-host "Successfully uninstalled $CurrentDisplayName $InstalledVersion via WMI" }
	        else {
                write-host "WMI uninstall retured an error, Trying to uninstall $CurrentDisplayName $InstalledVersion from registry entries"
	            if (-not(Test-Path ("Uninstall:"))){New-PSDrive -name Uninstall -psprovider registry -root HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall | Out-Null}
	            Get-ChildItem -Path Uninstall: | Where-Object -FilterScript { $_.GetValue("DisplayName") -like "*$CurrentDisplayName*"} | ForEach-Object -Process {
	                $CommandToRun = "msiexec"
	                $UNSTring = $_.GetValue("UninstallString").split("{")
	                $Parameters = "/X /Q {" + $UNSTring[1]
	                write-host "Running Command: " $CommandToRun $Parameters
	                Start-Process $CommandToRun $Parameters -wait -workingdirectory $WorkingDirectory | out-null
	            }
	            }
        }
    }
}

Second piece of code is a generic command to run script. This is basically just a wrapper for the Start-Process command. It can be used to run any command, but mostly I use this to start the msiexec.exe program with parameters. I can also use this command to start an setup.exe. Again, this is read from the config.xml. If there are arguments, then the second part of the conditional runs.

Function CommandToRunStep ($Step)
{
  if ($Step.Command.Value -ne "") {
    $Command = $Step.Command.Value
    if ($Step.Arguments.Value -ne "") {
      $Arguments = $Step.Arguments.Value
      write-host "Running Command: " "$Command" "$Arguments"
      Start-Process "$Command" -ArgumentList "$Arguments" -wait -workingdirectory $WorkingDirectory | out-null
    }
    Else {
      write-host "Running Command: " "$Command"
      Start-Process -FilePath "$Command" -wait -workingdirectory $WorkingDirectory | out-null
    }
  }
}

The final function takes a process name from the config xml file and kills it.

function KillStep ($Step)
{
    $ProcessName= ($Step.ProcessName.Value).split(".")[0]
    if ($ProcessName -ne "") {

    write-host "Killing: " "$ProcessName"
    Stop-Process -force -processname "$ProcessName" -ea SilentlyContinue
    }
}

The main part of this script loops through the xml file and call the correct function. The xml file can contain any number of the three types of functions above, and they are run in sequential order. This gives me the ability to create a “task sequence” in an xml file that will be run with a PowerShell script in an SCCM advertised program.

#  Main
$WorkingDirectory = Split-Path -parent $MyInvocation.MyCommand.Definition
[ xml ]$s = Get-Content $WorkingDirectory\Install.xml

foreach ($Step in $s.Install.Steps.Step)
{
switch ($Step.StepType.Value)
    {
    "UninstallOlderThan" {UninstallStep ($Step)}
    "CommandToRun" {  CommandToRunStep ($Step)}
    "KillProcess" { KillStep ($Step) }
    }
}

Updated
And an example of the XML file would be:

<Install>
<Steps>
  <Step>
	<StepType Value="UninstallOlderThan" />
	<CurrentDisplayName value="Apple Application Support" />
    	<CurrentVersion value="1.3.3" />
  </Step>
  <Step>
	<StepType Value="CommandToRun" />
	<Command Value="msiexec" />
	<Arguments Value="/i AppleApplicationSupport.msi /quiet /norestart" />
  </Step>
  <Step>
	<StepType Value="CommandToRun" />
	<Command Value="iTunesSetup.exe" />
	<Arguments Value="/quiet DESKTOP_SHORTCUTS=0" />
  </Step>
  <Step>
	<StepType Value="KillProcess" />
	<ProcessName Value="Process.exe" />
  </Step>
</Steps
</Install>

Powered by WordPress. Designed by WooThemes