PowerShell wrapper scripts for Exchange 2010 – first step: make a connection

As I talked about in this previous post, I like to write wrapper scripts that collect input and pass it along to the actual provided functions. I call these wrapper scripts because they are not really doing anything ground breaking, they are just a series of conditionals and commands that I put together, with a common naming convention. Then, all we have to do is tab completion through the scripts that I have written.

I wanted to do the same for creating new distribution groups in Exchange 2010, but first ,I needed to make  the Exchange 2010 PowerShell functions available on our local machines. I wrote the following function that starts a PSSession on the exchange server. This function will be called at the beginning of every Exchange wrapper script, guaranteeing that we have a connection to the Exchange PowerShell functions.

Here is that function:

Function JBMURPHY-EXCHANGE-StartPSSESSION {
if(! (Get-PSSession | Where-Object { $_.ComputerName -like "servername.company.com" })){
Write-Host "Createing PSSession to SVNYEXCH01.SARDVERB.LOCAL" -ForegroundColor Green
Import-PSSession (New-PSSession -Configurationname Microsoft.Exchange –ConnectionUri http://servername.company.com/powershell) | out-null
}
}

PowerShell script to email users if password expires soon, and send a summary to IT

I wanted to expand on my previous script: powershell-to-list-all-users-and-when-their-password-expires, so that it would send the user an email if their password was going to expire soon. Additionally I wanted to send a summary to our IT staff of accounts that were going to expire soon.

Here is that script:

$maxdays=(Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.TotalDays
$summarybody="Name `t ExpireDate `t DaysToExpire `n"

(Get-ADUser -filter {(Description -notlike "IfYouWantToExclude*") -and (Enabled -eq "True") -and (PasswordNeverExpires -eq "False")} -properties *) | Sort-Object pwdLastSet |
foreach-object {

$lastset=Get-Date([System.DateTime]::FromFileTimeUtc($_.pwdLastSet))
$expires=$lastset.AddDays($maxdays).ToShortDateString()
$daystoexpire=[math]::round((New-TimeSpan -Start $(Get-Date) -End $expires).TotalDays)
$samname=$_.samaccountname
$firstname=$_.GivenName
if ($daystoexpire -le 3){
	$ThereAreExpiring=$true

	$emailFrom = "[email protected]"
	$emailTo = "[email protected]"
	$subject = "$firstname, your password expires in $daystoexpire day(s)"
	$body = "$firstname,
	Your password expires in $daystoexpire day(s).

	Please press Ctrl + Alt + Del -> Change password"

	$smtpServer = "smtp.yourdomain.com"
	$smtp = new-object Net.Mail.SmtpClient($smtpServer)
	$smtp.Send($emailFrom, $emailTo, $subject, $body)

	$summarybody += "$samname `t $expires `t $daystoexpire `n"
}
}
if ($ThereAreExpiring) {
$emailFrom = "[email protected]"
$emailTo = "[email protected]"
$subject = "Expiring passwords"
$body = $summarybody
$smtpServer = "smtp.yourdomain.com"
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($emailFrom, $emailTo, $subject, $body)
}

Cisco ASA hacking – Getting started

My wife (Team Murphy’s CFO) allowed me to purchase a Cisco ASA 5505 for home. We use ASAs at work, and I am interested in how we can leverage these devices to their fullest. I am new to Cisco and to their IOS (the original IOS). I was impressed, the 5505 was easy to setup, I just swapped out my existing DDWRT and it worked out of the box. The 5505 had DHCP running on the internal interface, and NAT configured correctly.

Next I wanted to change the default network to use a different range. That proved more difficult when trying to do it over a network connection. I know I should be using the serial connection, but I did not want to sit in my coat closet to make the change. I also knew I would be hacking my config, and I wanted a scenario where I could early reset to the defaults. Every time I tried to reset to the defaults, the interface would hang because i was trying to do it over the network (as expected).

I found this link in the ASA documentation that described the default config for an ASA. I also found the environmental variable CONFIG_FILE. My thinking was, I could use the default config listed in the ASA documentation and save it to a file on the flash. If I wanted to return to the defaults, I could just change the  CONFIG_FILE environmental to point to that default config file on disk0:, and I would be back up and running in “factory defaults”.

I took it a step further and I added the following to my “jbmurphy_factory_defaults”:

ssh 192.168.XX.0 255.255.255.0 inside
ssh timeout 5
aaa authentication ssh console LOCAL
ssh scopy enable

These 4 lines enable ssh access to the ASA and allow me to scp my config file to disk0. Now, when I am working on a new config, I can scp it from my filesystem to the onboard storage, and issue a reboot.

That is my workflow so far. Thoughts?

VMware ESX suspended machine won’t resume because of missing hardware

We recently had some downtime to physically move a rack for our server room expansion. One of the servers we were shutting down was a VMware ESX host. When I tried to resume the machine, I received an error saying “Module DevicePowerOn power on failed. Unable to open the SCSI device . . .”

We had removed a daisy chained tape device, and I forgot! I wanted to remove it from the hardware profile, but I couldn’t because the machine was suspended.

So  . . . , I can’t power on, I can’t power down, and I can’t edit the hardware profile because the machine is suspended. Crap.

I sshed into the ESX box and tried: vmware-cmd stop hard. I received the error : “The attempted operation cannot be performed in the current state . . .”.

Crap.

The only way I could get the machine powered on, was to comment out the missing scsi device in the VM’s vmx file. After that, I could power on the machine no problem. Hope that may help someone!

PowerShell Active Directory wrapper scripts.

In my PowerShell profile I have a routine the sources all the *.ps1 files in a folder. One of these .ps1 files contains Active Directory “wrapper” functions. I call them “wrapper” functions because they simplify the parameters needed to return data in a format we want. The idea is that I can write a series of “wrapper” scripts, alias them, and then everyone on our team can use the same script. All a user has to do is type “jbmurphy” (if that is the script starts with) and hit TAB  and the command completion will cycle through all the wrapper scripts. Some of the scripts are not much different than the actual “wrapped” script, but there usually is simplified formatting to just dump the needed info. Here are two wrapper scripts to interact with ActiveDirectory (AD).

First one finds users in a group:

function jbmurphy-GetGroupMembers {
    Param($GroupName)
    return Get-ADGroupMember $GroupName | ft -hidetableheaders name
}

Second one finds the groups a users is a member of:

function jbmurphy-AD-GetGroupMembership {
Param($UserName)
   $user=Get-ADUser -properties memberof $UserName
   return $user.MemberOf -replace '^cn=([^,]+).+$','$1'
}

PowerShell script to silently install RSAT 2008 R2 SP1

I often re-image my machine – that way I know my SCCM OSD is working properly and has the most recent “secondary apps” (Reader,Flash,FireFox . . .). Because I am always re-imaging, I like to automate installing the software I always need like Remote Server Administration Tools – RSAT. I wanted a function to install the software and automatically add all the features. Below is that script:

function Install-RSAT {
$ScriptPath = "\\server\sahre\path\to\RAST\installers\"
Set-Location $ScriptPath

$os=(Get-WMIObject win32_operatingsystem).OSArchitecture
$sp=(Get-WmiObject Win32_OperatingSystem).CSDVersion
if (($os -eq "64-bit") -and ($sp -eq "")) {$files = get-childitem -recurse -filter amd64*.msu}
if (($os -eq "32-bit") -and ($sp -eq "")) {$files = get-childitem -recurse -filter x86*.msu}
if (($os -eq "64-bit") -and ($sp -eq "Service Pack 1")) {$files = get-childitem -recurse -filter *KB958830*amd64*.msu}
if (($os -eq "32-bit") -and ($sp -eq "Service Pack 1")) {$files = get-childitem -recurse -filter *KB958830*x86*.msu}

foreach ($file in $files)
{
Start-Process -wait -FilePath "C:\WINDOWS\SYSTEM32\wusa.exe" -ArgumentList "$file.FullName /quiet /norestart"
}

DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-AD /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-AD-DS /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-AD-DS-SnapIns /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-AD-DS-AdministrativeCenter /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-AD-DS-NIS /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-AD-LDS /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-AD-Powershell /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-ServerManager /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-CertificateServices /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-CertificateServices-CA /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-CertificateServices-OnlineResponder /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-DHCP /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-DNS /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-FileServices /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-FileServices-Dfs /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-FileServices-Fsrm /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-FileServices-StorageMgmt /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-HyperV /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Roles-RDS /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Features /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Features-BitLocker /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Features-Clustering /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Features-GP /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Features-LoadBalancing /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Features-SmtpServer /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Features-StorageExplorer /Quiet /NoRestart
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Features-StorageManager
DISM /online /Enable-Feature /FeatureName:RemoteServerAdministrationTools-Features-Wsrm
}

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
}
}

Powered by WordPress. Designed by WooThemes