Archive | PowerShell

PowerShell to set ACLs that prevent renaming of top-level directories in a share

We have a shared folder that is replicated to all our sites using the fantastic GlobalScape WAFS (@GlobalScape). This folder is huge.

People go into the folder, and “type ahead” to get the subfolder they want,  but what keeps happening (and I am sure you have seen this) is that when users click and try to type ahead, they don’t realize that they have clicked a folder and their “type ahead” is actually renaming the folder. We end up with a lot of folders named “p” or “j” or “m”. Everyone has full access to the folder and all files/folders below. The impact is that when a large folder gets renamed, this has to be replicated across the country via WAFS, which is slowing down the other offices.

To solve this I had to sit down and figure out ACLs and using PowerShell to change them. The security on the top-level folders are inherited from above, and they are simple: Administrator = FULL, System = Full, Creator Owner = Modify, and BUILTIN\USERS = Modify.

The script below, loops through the top-level folders (in this case just the A’s) and  copies the inherited permissions to the folder. Next it sets the folder so it does not inherit permission from the folder above (Lines 1 – 6). Then, the script  creates an ACL rule that removes the BUILTIN\USERS = Modify ACL (Line  8 ). Finally it creates 2 ACL rules that add, for BUILTIN\USERS,  “ReadAndExecute” for “This folder only” and a “Modify” for “Subfolder and files below” (Lines 9 – 10).

And this is what the result looks like:

PowerShell script:

Get-ChildItem a* | Where {$_.psIsContainer -eq $true} | foreach {
$filename = $_
write-host "Changing $_"
$objACL=Get-ACL $filename
$objACL.SetAccessRuleProtection($true,$true)
set-acl $filename $objACL

$RMAccessRule = New-Object Security.AccessControl.FileSystemAccessRule("BUILTIN\Users",@("Modify", "Synchronize"),"ContainerInherit, ObjectInherit","None","Allow")
$AddAccessRule1 = New-Object Security.AccessControl.FileSystemAccessRule("BUILTIN\Users",@("ReadAndExecute", "Synchronize"),"None","None","Allow")
$AddAccessRule2 = New-Object Security.AccessControl.FileSystemAccessRule("BUILTIN\Users",@("Modify, Synchronize"),"ContainerInherit, ObjectInherit","InheritOnly","Allow")

$objACL=Get-ACL $filename
$objACL.RemoveAccessRule($RMAccessRule)
$objACL.AddAccessRule($AddAccessRule1)
$objACL.AddAccessRule($AddAccessRule2)
set-acl $filename $objACL
}

PowerShell, Event Triggers, MailboxMoves and email notification

We are in the process of moving our Mailboxes from Exchange 2003 to Exchange 2010. I am using this script to automate the moves. I wanted to find a way to get an email notification when the move is complete. I figured I could keep the PowerShell script  running in a “while loop” until Get-MailBoxMoveStatistics reports “Completed”, but I wanted to write a more generic notification script.

One of the features that I had been itching to play with in 2008 is the “attach a task to this event”, so I wrote the following generic PowerShell script to receive a set of values from a triggered event. This could be used for any event that you can attach a task to. Big picture is that I am passing the event id to the script, and then using PowerShell to query the event log to get the rest of the log entry. This is then emailed.

Before we get to the script, by default, the scheduled task created by the wizard does not pass any values to the script it is running. I found this article that described how to modify your “Event Viewer Task” to pass values to the attached action. Use the method he describes to export the task and the re-import with the following :

      <ValueQueries>
        <Value name="eventData">Event/EventData/Data</Value>
        <Value name="eventRecordID">Event/System/EventRecordID</Value>
      </ValueQueries>

The actual command that is attached to the event is:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command SendEventRecord.ps1 -subject "MailBox Move Completed" -eventid $(eventRecordID)

And here is the PowerShell script called SendEventRecord.ps1. This script received paramaters from the command above. This script uses the eventRecordID in the event log item to look up the actual error and email the contents.

Param($Subject, $Body,$EventID)

$Body += Get-EventLog -LogName Application | Where-Object {$_.Index -eq $EventID} | fl | out-string

$emailFrom = "[email protected]"
$emailTo = "[email protected]"
$emailsubject = $Subject
$emailbody = $Body
$smtpServer = "server.ip.address"
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($emailFrom, $emailTo, $emailsubject, $emailbody)

Now, when a 1107 appears in the event log, the eventRecordID is passed to a PowerShell script that looks up the record/event and emails it to the right people! I like this one.

PowerShell to assign Exchange 2010 Retention Policies based on AD group membership

In exchange 2003 we maintained a 6 month and 12 month purge policy that was applied based on group membership. I described that configuration here. I wanted to migrate our purge policies to the new 2010 Recipient policies. I needed to find the members of an AD group and assign a retention policy to their mailbox. Here is my script:

Get-ADGroupMember mailboxpurge-6m | foreach {
if ((get-mailbox $_.samaccountname).ServerName -eq "server01" -OR (get-mailbox $_.samaccountname).ServerName -eq "server02" )
{
write-host "User" + $_.samaccountname + "'s current policy is: " + (get-mailbox $_.samaccountname).RetentionPolicy
set-mailbox $_.samaccountname -RetentionPolicy 6MonthPurge
}
}

Scheduled PowerShell script to resume Mailbox Moves (New-MoveRequest)

Moving mailboxes from 2003 to 2010 is easy, but there is not a “Gui” to schedule it. So I created the following PowerShell script to find all the suspended MoveRequest(s) with the correct date, and resume them.
MailboxMove.ps1:

$TodaysDate = (get-date).day.ToString() + (get-date -format MMMM)
Get-MoveRequest -MoveStatus Suspended | Get-MoveRequestStatistics | Where {$_.BatchName -like "*$TodaysDate*"} | Resume-MoveRequest

Next I create a scheduled task with the following command to run a script with the code above:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command ". 'C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1'; Connect-ExchangeServer -auto; c:\Scripts\MailboxMove.ps1"

Now when I create a New-MoveRequest,

New-MoveRequest -Identity username -TargetDatabase MailboxDatabase02 -BatchName "19August" -BadItemLimit 10 -Suspend

I know that a scheduled task will resume the move a the correct time.

To monitor progress, I have been using this script:

Get-MoveRequest  | Get-MoveRequestStatistics | ft -auto alias,Status,TotalMailboxSize,PercentComplete,TargetDatabase,TotalInProgressDuration,BytesTransferred,BatchName

PowerShell Function to get uptime on multiple computers

I wanted to create a function that I could use to find the uptime of several workstations. I did not want to read a list of machine name from csv, I just wanted pass a list of workstation names and get their uptime back. I also added a ping check to make sure the machine is alive.

 

Function Get-Computer-LastBootTime {
$Args | ForEach-Object -Process {
$ping = gwmi Win32_PingStatus -Filter ("Address='" + $_ + "'") | Select-Object StatusCode
if ($ping.statusCode -eq 0) {

$wmi = gwmi Win32_OperatingSystem -EA silentlycontinue -ComputerName $_
$localdatetime = $wmi.ConvertToDateTime($wmi.LocalDateTime)
$lastbootuptime = $wmi.ConvertToDateTime($wmi.LastBootUpTime)
$uptime = $localdatetime - $lastbootuptime
$days=$uptime.Days
$hours=$uptime.Hours
$mins=$uptime.Minutes
echo "$_ uptime: $days days $hours hours $mins mins"

}
else {
echo "$_ is offline"
}
} 
}

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 "*[email protected]"){$_.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