Enable AD Inheritance on all users in an OU and all sub OUs

Sometimes when we are deploying Exchange for our customers, we run into problems where Mobile Device (ActiveSync) users can logon to the OWA site, but can’t retrieve mail. They get a “Cannot get mail” error or similar. This occurs for iOS and Android users. This usually happens to Administrative users (best practice is to have administrator roles and user roles separate, but you can’t tell some people!) because they are not allowed to inherit rights by design!

So I wrote a script to allow inheritance for all users in a given AD OU. You will need to have the affected user(s) connect to ActiveSync soon after running this script, as the Administrative users will have this setting revert every so often.

<# Purpose: To enable inheritance for all AD users in the specified OU and all sub-OUs. 
Author: Michael Kenning (mjkenning@gmail.com) 
Version: 1.1 (release) 
Updated: 21 FEB 2015 
Notes: Change variables as needed 
#>

### VARIABLES ###
$searchOU = "ou=OUNAME,dc=DOMAIN,dc=COM"
### END VARIABLES ###

$users = Get-ADUser -ldapfilter "(objectclass=user)" -searchbase $searchOU
$changed = 0
$same = 0

ForEach($user in $users)
{
    # -- Get the full path to the user object
    $ou = [ADSI]("LDAP://" + $user)
	
	# -- Get the security information for the user object
    $sec = $ou.psbase.objectSecurity
 
	# -- Change the security settings for the user
    if ($sec.get_AreAccessRulesProtected())
    {
        $isProtected = $false 			## allows inheritance
        $preserveInheritance = $true 	## preserve inherited rules
        
		# -- Make the change!
		$sec.SetAccessRuleProtection($isProtected, $preserveInheritance)
        $ou.psbase.commitchanges()
        
		# -- Let the console know that the user was changed
		Write-Host "$user is now inheriting permissions";
		
		# -- Increment the changed user count
		$changed += 1
    }
    else
    {
		# -- Let the console know that the user didn't need to be changed
		Write-Host "$User Inheritable Permission already set"
		
		# -- Increment the unchanged user count
		$same += 1
    }
}

# -- Give a summary of changes
Write-host "The number of changed users is $changed"
Write-host "The number of unchanged users is $same"
Tagged with: , , ,
Posted in Powershell

Get a dump of all DNS records from a DNS server

Have you ever needed to get a list of all records from your DNS server? Maybe you need to migrate the records to a new server (powershell can do that, too!) or maybe you just need it for your documentation. Whatever the reason, I wrote a little script to get the information. Change the destination / file name at the end of the script as needed.

$dnszones = Get-DnsServerZone | Select ZoneName
$OutArray = @()

ForEach ($zone in $dnszones) {
	$ZoneName = $Zone.ZoneName
	$ZoneData = Get-DnsServerResourceRecord $ZoneName | ?{$_.RecordType -eq "A"}
	ForEach ($record in $zoneData) {
		$HostName = $record.HostName
		$ipAddress = $record.RecordData.ipV4Address

		$OutArray += New-Object psCustomObject -Property @{
		'Zone' = $ZoneName
		'HostName' = $HostName
		'IPAddress' = $ipAddress
		} | Select Zone, HostName, IPAddress
	}
}
$OutArray | Export-Csv -NoTypeInformation c:\temp\DNSRecords.csv
Tagged with: , ,
Posted in Powershell

Remotely set DNS servers on Windows clients

One of the things that we do for our customers is to refresh their virtual environments with new servers (latest OS, etc). When we do this migration, we almost always replace the DNS servers. This can cause significant work to change existing (and new) servers to the new DNS IPs. Enter PowerShell!

<#
s e t D N S . p s 1 

Purpose:		Change the DNS servers on remote computers bases on list in a text file.
Author:			Michael Kenning <mjkenning@gmail.com>
Version: 		1.1 (16 NOV 2014)

Usage:			.\setDNS.ps1 [INPUT FILE]
Example: 		.\setDNS.ps1 .\file.txt

Requirements:
	1) PowerShell installed on executing machine
	2) Credentials: The executing user must have admin rights on the target machines

#>

param (
	[Parameter(Mandatory=$true,Position=0)]
	[ValidateNotNullOrEmpty()]
	[String]
	$path
)

$servers = Get-Content $path
$newDNS = "10.1.1.1","10.2.1.1"
 
foreach($server in $servers) {
	Write-Host "Connecting to $server..."
	$nics = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $server -ErrorAction Inquire | Where{$_.IPEnabled -eq "TRUE"}
	 
	foreach($nic in $nics) {
		Write-Host "`tExisting DNS Servers " $nic.DNSServerSearchOrder
		$x = $nic.SetDNSServerSearchOrder($newDNS)
	 
		if($x.ReturnValue -eq 0) {
			Write-Host "`tSuccessfully Changed DNS Servers on " $server
		}
		else {
			Write-Host "`tFailed to Change DNS Servers on " $server
		}
	}
}
Tagged with: , ,
Posted in Powershell

Create AD groups from a list of users

While working with a customer, we needed to add several AD groups. Each group had many users that were identified by the customer. For each group, I created a txt file with the list of user names and then wrote a script to create the group and add the users.

For this organization, we had a single OU to place the groups. The $groupPath variable would need to be changed to reflect the LDAP path of that location.

<#
Purpose: To create an AD group and add members.
Author: Michael Kenning (mjkenning@gmail.com)
Date: 28 OCT 2014
Version: 1.3

Usage: ./new-adgroup.ps1 LIST_OF_USERS.TXT "Group Name"
#>

param (
	[Parameter(Mandatory=$true,Position=0)]
	[ValidateNotNullOrEmpty()]
	[String]
	$path,
	[Parameter(Mandatory=$true,Position=1)]
	[ValidateNotNullOrEmpty()]
	[String]
	$groupName
)

# Change this variable to specify the location of the group in AD
$groupPath = "OU=Application Groups,DC=DOMAIN,DC=COM"

Import-Module ActiveDirectory
$groupMembers = get-content $path

# Check to see if the group exists and add the group if it does not.
$groupStatus = get-adgroup $groupName | Out-Null
if ($groupStatus -eq $null) {
	new-adgroup -Name $groupName -Path $groupPath -GroupScope Universal -GroupCategory Security
}

# Add users to the group
foreach ($member in $groupMembers) {
	Add-ADGroupMember $groupName $member
}
Tagged with: , ,
Posted in Powershell

Automatically create multiple hard drives on separate controllers in VMware

As mentioned earlier, my place of business sells and supports AppAssure. This means we have to create large volumes for the repository. For me, it became tedious to manually add these large drives, especially because of the need to power off the VM, set the SCSI LUN to the correct number, etc, etc. So, I created a script to do it for me!

<#
Purpose:	Script to add multiple SCSI hard drives for guest
Author:		Michael Kenning (mjkenning@gmail.com)
Version:	1.4 (13 AUG 2014)
Notes:		Be sure to change the Variables!
#>

## VARIABLES ##
$vmName = 'VMNAME'
$myHost = 'HOST.DOMAIN.COM'
$hdSize = '10500' # Size in GB
$numDisks = '1'
$datastore = 'DATASTORE'
$format = 'EagerZeroedThick '
$controllerType = 'Paravirtual'
## END VARIABLES ##

# --- Connect to the host
$error.clear()
try {
	Connect-VIServer -Server $myHost -ErrorAction Stop
}
catch [Exception]{
	Write-warning "Connecting to the vCenter server failed. Please check credentials and try again."
}
if ($error) {
	$Null = Disconnect-VIServer -Server * -confirm:$False
	Throw $error
}

$vm1 = Get-VM -Name $vmName

if ($vm1.PowerState -eq "PoweredOn") {
	$initialState = "On"
	Write-Host "Shutting Down" $vmName
	Shutdown-VMGuest -VM $vmName -Confirm:$false
	#Wait for Shutdown to complete
	do {
		#Wait 5 seconds
		Start-Sleep -s 5
		#Check the power status
		$vm1 = Get-VM -Name $vmName
		$status = $vm1.PowerState
	} until ($status -eq "PoweredOff")
}

# Create the Hard Disk and Controller
$vm2 = get-vm $vmName
$vm2 | New-HardDisk -CapacityGB $hdSize -Datastore $datastore -StorageFormat $format| New-ScsiController -Type $controllerType
$hd = $vm2 | Get-HardDisk | select -last 1

# Edit the controller to be the correct LUN or "Unit Number"
$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$spec.deviceChange = New-Object VMware.Vim.VirtualDeviceConfigSpec[] (1)
$spec.deviceChange[0] = New-Object VMware.Vim.VirtualDeviceConfigSpec
$spec.deviceChange[0].operation = "edit"
$spec.deviceChange[0].device = $hd.ExtensionData
$spec.deviceChange[0].device.unitNumber = 0

$vm2.ExtensionData.ReconfigVM_Task($spec)

if ($initialState -eq "On") {
	Start-VM $vmName -Confirm:$false
}
Tagged with: ,
Posted in PowerCLI

Programmatically set folder/file ownership recursively

Recently, I had the task of unraveling a messy file system for a customer. Their permissions were not well maintained and individual folder ownership was set to various deleted accounts or missing altogether. So, I created a script that would clean the ownership bit up, thus allowing an administrator to at least see what is happening to the folders and files.

<#
Purpose:	Script to set ownership on a folder and it's subfolders/files
Author:		Michael Kenning (mjkenning@gmail.com)
Version:	1.0 (17 JUNE 2014)

Notes:		

Possible Rights to assign via ACL:
	AppendData
	ChangePermissions
	CreateDirectories
	CreateFiles
	Delete
	DeleteSubdirectoriesAndFiles
	ExecuteFile
	FullControl
	ListDirectory
	Modify
	Read
	ReadAndExecute
	ReadAttributes
	ReadData
	ReadExtendedAttributes
	ReadPermissions
	Synchronize
	TakeOwnership
	Traverse
	Write
	WriteAttributes
	WriteData
	WriteExtendedAttributes
#>

$DomainName =  "[Domain Name]"
$HomeDirectoryPath = "\\[Servername]\E$\Users"
$SubDirectories = (Get-ChildItem $HomeDirectoryPath)

Foreach ($FolderName in $SubDirectories) {
	$owner = New-Object System.Security.Principal.NTAccount('BUILTIN\Administrators')
	$objUser = New-Object System.Security.Principal.NTAccount("$DomainName","$FolderName")
	
	#Set Parent Folder Ownership
	Get-Item -LiteralPath "$HomeDirectoryPath\$FolderName" -ErrorAction SilentlyContinue | Get-Acl |
	ForEach-Object {
		$_.SetOwner($owner) 
		Set-Acl -aclobject $_ -Path $_.PSPath
		$Path = Split-Path $_.Path -NoQualifier
		Write-Host $Path "Folder Owner Set -" $owner
	}
	#Set Child Item Ownership
	Get-ChildItem -LiteralPath "$HomeDirectoryPath\$FolderName" -Recurse –ErrorAction SilentlyContinue | Get-Acl | 
	ForEach-Object {
		$_.SetOwner($owner) 
		Set-Acl -aclobject $_ -Path $_.PSPath
		$Path = Split-Path $_.Path -NoQualifier 
		Write-Host $Path "File Owner Set -" $owner
		}
}
Tagged with: ,
Posted in Powershell

Dell AppAssure: Find offline agents and restart the agent service

At my place of business, we sell and support Dell AppAssure for most of our customers. Recently, Dell released the 5.4.1 version and I noticed something interesting. Occasionally, agents would go “offline” in the Core console. Untill I have a chance to figure out why this is happening, I wrote a script to take advantage of the AppAssure PowerShell APIs and restart the remote AppAssureAgent service.

AACore-AgentsOffline

At first, I wrote a script that searched through the AppAssure event log for the error stating which agents were offline in the past hour (the snapshot interval). However, this proved to be rather messy. Perhaps it’s because I don’t know enough about parsing event log messages, but it was hard to get the exact Agent’s computer name from the event message.

Here’s the code from my initial attempt, which includes code to create yet another event log entry telling me that the agent was restarted:

<#
Purpose:	Script to find AppAssure events that indicate an agent is not responding, then restart that service remotely.
Author:		Michael Kenning (mjkenning@gmail.com)
Version:	1.1 (15 MAY 2014) -- (Added code to create event log entry and send an alert email)
Usage: 		.\fixAAagent.ps1

Notes:		AppAssure event ID 35291 indicates a missed snapshot because the agent wasn't responding.
			Tested on AppAssure Core version 5.4.1.77
			The display names of all protected machines should not have the "or" or "has" strings in them as this will break event detection.
			Must create an event log source prior to executing this script, matching the source in the code below:
				e.g. New-EventLog –LogName Application –Source “Fix AA Agent"

#>

# Determine the time to search the log file
$startTime = (Get-Date) - (New-TimeSpan -Hour 1) # Start time is set to one hour prior to the current time. This can be adjusted based on need.
$endTime = (Get-Date) # End time is the current time

# Find the events we are looking for!
$message = @(Get-WinEvent -FilterHashtable @{logname='AppAssure'; id=35291;StartTime=$startTime;EndTime=$EndTime} | Select Message) # You can adapt this to search other logs and for other event ID's.

# Process each message and extract the hostname, then trim the string into a usable variable to pass to the restart code.
foreach ($item in $message){
	$pattern = "(([^on)]+)has)" # This regex pattern will return the text after the word "on" up to and including the word "has".
	if ($item -match $pattern) { # Only execute the code below if there is text matching the pattern
		$computerName = $matches[0] # Return the match and assign it to a variable
		$computerName = $computerName.trim() # Trim spaces from the beginning and end of the variable
		$computerName = $computerName.trimend("has") # Trim the letters found at the end of the string
		$computerName = $computerName.trim() # Trim spaces from the beginning and end of the variable
	}

	# Restart the service, create an event log and send an alert email.
	if ($matches) { # Check to make sure we found a match before we restart anything!
		$ServiceObj = Get-Service -Name "AppAssureAgent" -ComputerName $computerName -ErrorAction Stop # Set the action specifications to a variable.
		$result = Restart-Service -InputObj $ServiceObj # Restart the service based on the action object defined above.
		
		$EventLogEntry = "==============================`nSummary Information:`nAgent found with broken connection:`n$computerName`nResult was: $result"
		Write-EventLog -LogName Application -Source "Fix AA Agent" -EntryType Warning -EventID 1 -Message $EventLogEntry
		
	}
}

This did the job reasonably well, but I couldn’t use agent names with the “or” string in them. This customer has a “.org” domain name, which meant I had to tidy up all the display names to be host name only. Like I said, messy and not what I was looking for.

Here’s my attempt with the AppAssure API’s. Note, if you haven’t already done so, you will need to run the following command:

Import-Module “AppAssurePowerShellModule”
<#
Purpose:	Script to find AppAssure agents that are not responding, then start or restart that service remotely.
Author:		Michael Kenning (mjkenning@gmail.com)
Version:	1.0 (15 MAY 2014)
Usage: 		.\fixAAagent.ps1

Notes:		Tested on:
		1) AppAssure 5.4.1
		2) Windows 2012 R2 (Powershell v4)
#>

# Determine which Agents are not "Online"
$servers = Get-ProtectedServers | where Status -ne Online | Select -expand displayname

foreach ($server in $servers){
 	# Get the Agent service status of the servers identified as not online.
	$status = (Get-Service AppAssureAgent -Computer $server).Status

 	# If the service is anything but "Running", start the service
	if ($status -ne "Running"){
		$ServiceObj = Get-Service -Name AppAssureAgent -ComputerName $server -ErrorAction Stop
		Start-Service -InputObj $ServiceObj
	}
	# If the service is in a "Running" status, restart the service.
	Else {
		$ServiceObj = Get-Service -Name AppAssureAgent -ComputerName $server -ErrorAction Stop
		Restart-Service -InputObj $ServiceObj
	}
}

As you can see, this script is much cleaner and neater. I set this script to run in a scheduled task every hour, just before the snapshots take place.

Tagged with: ,
Posted in AppAssure, Powershell