Can’t change password – The password does not meet the password policy requirements.

Ever have a user call and complain that they can’t change their password?

They get an error message “The password does not meet the password policy requirements.” but they promise that the new password is complex?

Any you try and get the same thing?

Could be a minimum password age policy issue…

 

 

This powershell will lookup your domain password policy and match it against a user and the last password change.

 

I am posting this mostly because I had a hard time retrieving the  domain policy parts.

$Domain.maxPwdAge.Value kept returning an object of System.__ComObject

It took a lot of searching to eventually find a way to get the actual value out.

I am sorry I didn’t save the links to give credit… At the same time anyone who can explain this to all of us I would sure like to understand why $lngMaxPwdAge = $Domain.ConvertLargeIntegerToInt64($MPA) works and $mpa.value does not…

$MPA = $Domain.maxPwdAge.Value
<em id="__mceDel">$lngMaxPwdAge = $Domain.ConvertLargeIntegerToInt64($MPA)</em>

 

 

Either way here is the script that will tell you the policy and when a password was last changed.

It will show an alert if the password is under the min or over the max password age.

cls
$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')
While (!$result)
{
$UserName = Read-Host 'Username to check : '
if (!$UserName)
{
Write-Host "No Username Entered"
exit
}
$searcher.Filter = "(&amp;(objectClass=User)(samAccountName=" + $username + "))"
$result = $searcher.Findone()
}
# get domain password policy (max pw age)
$D = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$Domain = [ADSI]"LDAP://$D"
$MPA = $Domain.maxPwdAge.Value
$MinPA = $Domain.minPwdAge.Value
# Convert to Int64 ticks (100-nanosecond intervals).
$lngMaxPwdAge = $Domain.ConvertLargeIntegerToInt64($MPA)
$lngMinPwdAge = $Domain.ConvertLargeIntegerToInt64($MinPA)
$MinPwdLength = $Domain.minPwdLength
$PwdHistory = $Domain.pwdHistoryLength
# Convert to days.
$MaxPwdAge = -$lngMaxPwdAge/(600000000 * 1440)
$MinPwdAge = -$lngMinPwdAge/(600000000 * 1440)
$lngPwdLastSet =$result.Properties.pwdlastset
$pwdLastSet = [datetime]::FromFileTime($lngPwdLastSet[0])
Write-Host $result.Path
Write-Host $result.Properties.cn " " $result.Properties.userprincipalname
Write-Host "Password Last Set : " $pwdLastSet
Write-Host "Max Password Age : " $MaxPwdAge
Write-Host "Min Password Age : " $MinPwdAge
Write-Host "Password History : " $PwdHistory
Write-Host "Min Password Length : " $MinPwdLength
if ($pwdLastSet -ge (Get-Date).AddDays(-$MinPwdAge)){Write-Host -ForegroundColor Red "Password can not be changed - Min Age"}
if ($pwdLastSet -ge (Get-Date).AddDays($MaxPwdAge)){Write-Host -ForegroundColor Red "Password Expired"}

Enjoy.

 

Report on SIDHistory

This script will use PowerShell to check your domain for any users or groups that have a SID history, and will report the SID History, current SID and username to c:\UserSid.csv

You can use the current domain or specify something specific.

 

cls
$strFilter = "(&amp;(|(objectCategory=User)(objectCategory=Group))(sidHistory=*))"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
#$objSearcher.SearchRoot = "LDAP://dc=domainname, dc=com" #or manually override domain here
$objSearcher.PageSize = 100
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
"OldSID,CurrentSid,OldName,NewName" | Out-File c:\UserSid.csv
$colProplist = "name" , "objectsid", "sidhistory"
foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)| Out-Null}
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults)
{
$UserSid = New-Object System.Security.Principal.SecurityIdentifier($objResult.Properties.objectsid[0], 0)
foreach ($sidhistory in $objResult.Properties.sidhistory)
{
$OldUserSid = New-Object System.Security.Principal.SecurityIdentifier($sidhistory, 0)
$sid = new-object System.Security.Principal.SecurityIdentifier($usersid)
$user = $sid.Translate([System.Security.Principal.NTAccount])
if ($user)
{
$outstring = $OldUserSid.Value + "," + $UserSid.Value + "," + $objResult.Properties.name + "," + $user.value
}
else
{
$outstring = $OldUserSid.Value + "," + $UserSid.Value + "," + $objResult.Properties.name + ",Not Found"
}
$outstring | Out-File -append c:\UserSid.csv
}
}

List all the groups a user is a member of … Thank-You PowerShell

Let me start by saying that if you still cling to vbscript (as I did for so long) I have to say come to the dark side we have cookies. Seriously things are so much better once you get it figured out.

For whatever reason I started working with arrays in this script and learned a few cool things.

Remember how hard it was to actually remove an item from an array?
$array.remove(object)
and this is done using the value of the array member so you don’t even need to know it’s address!

recall having to write whole loops to bubble sort your array? another one liner now.

[Array]::Sort([array]$ArrayName)

And perhaps my favorite in this script, how to loop through an array and see if it contains a value, again a single line.

$array -contains "value"

The other thing I do a lot in this script is work with AD objects. Search for a user find all the groups they are a member of then bind each group and check its members etc etc. Lots of good examples of how to get a handle on an AD object. took me a long time to get it right but once you do it’s easy.

$Object= [ADSI]("LDAP://"+ $x)

where $x is the distinguished name of the object…. don’t know the DN? use the searcher

$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')
$searcher.Filter = "(&amp;(objectClass=User)(samAccountName=$SAMAccountName))"
$result = $searcher.Findone()

The only trick now that you have the name is actually getting the AD object to work with.

$user=$result.GetDirectoryEntry()

Starting to enjoy PowerShell yet?

# for more info s@blackops.ca
#The script will prompt you for a user logon name (no domain)
#If you click cancel or don’t enter a name the script will exit
#The script will continue to prompt you until it gets a valid username.
#The script will then load all the groups the user is a member of and go through all the nested groups reporting the hierarchy and noting any groups the use is a member of more than once.
#At the end it will output a single list sorted by group name to make it easier to find a specific group.
# This is the core function it takes an array of AD groups via distinguished name and a depth field to help with spacing in the output. For each group that is a member of a previous group it will call itself with the new array and depth +1
function GroupEnnumerate ([System.Collections.ArrayList]$InputArray, [int]$Depth)
{
$spacer=""
for ($i=1;$i -le $Depth; $i++) {$spacer=$spacer + "`t"}
#the two above lines add a tab char foe each level deep to keep the output formatted
While ($InputArray.count -gt 0) #repent until the input array is empty
{
#The next line is important to make sure we have not already processed a group.
#This prevents an endless loop if a group is a member of itself either directly or indirectly
if (!($expandedgrouparray -contains $InputArray[0]))
{
$GroupName = $InputArray[0]
$expandedgrouparray.add($InputArray[0])|Out-Null #add the new group to the array of processed groups
$Group = [ADSI]("LDAP://"+ $groupname) #lookup the group in AD
$expandedgrouparraynames.add($group.name.tostring())|Out-Null #add the group friendly name to an array for the output at the end
Write-Host $spacer($Group.Name) #display the group in the output
$TempGroupArray = New-Object System.Collections.ArrayList $null #blank and init the temp array of sub groups
foreach ($x in $group.memberof) # for each sub-group do the following
{
$member = [ADSI]("LDAP://"+ $x) #lookup the memberof  in AD
If ($member.SchemaClassName -eq "group") #we don't care if the member is not a group, just in case...
{
$TempGroupArray.add($x)|Out-Null #add the sub-group to a temp array to be used when calling the function for the next loop
}
}
if ($TempGroupArray.Count -gt 0) #if we found any sub-groups start checking them
{
[Array]::Sort([array]$TempGroupArray) # sort the array so he output is easier to read
GroupEnnumerate $TempGroupArray ($Depth+1) # call the function again with the temparray if sub-groups and add another tab to the output
$tempGroupArray=$null #blank out the temp array
}
$InputArray.remove($InputArray[0]) #as we process each group in the input array we need to remove it
}
else
{
#If we have already processed the group once then just report and remove it from the input array
write-Host -ForegroundColor Red $spacer([ADSI]("LDAP://"+ ($InputArray[0]))).name "- Already a member"
$InputArray.remove($InputArray[0])
}
}
}
cls
$StartGroupArray = New-Object System.Collections.ArrayList
$ExpandedGroupArray = New-Object System.Collections.ArrayList
$ExpandedGroupArrayNames = New-Object System.Collections.ArrayList
#I create the arrarys as above so I can use the .add .remove etc
$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')
$user=$null
$PromptMessage="Please enter the user logon name"
#the following do loop continues until you don't enter anything or you enter a good username
do
{
$SAMAccountName=$null
$SAMAccountName = Read-Host $PromptMessage
if ($SAMAccountName) #if there was ANY input in the box
{
$searcher.Filter = "(&amp;(objectClass=User)(samAccountName=$SAMAccountName))"
$result = $searcher.Findone() # search AD for the requested user
if ($result)
{
$user=$result.GetDirectoryEntry()
# if we find a user in ID then get the AD object to work with later
}
else
{
# if we don't find a user update the message prompt so the user knows something happened
$PromptMessage= "User " + $samaccountname + " not found - Please enter the user logon name"
}
}
else
{
exit #if there was not ANY input in the box quit the whole script
}
}
while (!$user) # until we have a valid user object in AD keep pestering the operator
$groups = $user.memberof # get a list of all the groups that this user is a member of
foreach($group in $groups)
{
#for each group that they are a member of add the group DN to the array for processing
$StartGroupArray = $startGroupArray + $group
}
Write-Host "User: " $user.displayName " " $user.sAMAccountName " is a member of:" #this is the header of the output
[Array]::Sort([array]$startGroupArray)#fort the array so the output is easier to read
GroupEnnumerate $startGroupArray 1 #initiate the top-level group check
Write-Host # space in the output
Write-Host "Complete list sorted by group name" #second output header
$ExpandedGroupArraynames | Sort-Object #sorted list of all the groups processed that the user is a member of.
Write-Host # space in the output
Write-Host "Script Complete"

File System Audit and the WABAC machine – Now with set-acl

‘Twas a long time ago, longer now than it seems
In a place that perhaps you’ve seen in your dreams.
For the story that you are about to be told
Began with the technicians of old.

A very long time ago, as a junior tech I got a seemingly simple request.  “Tell me what user X has access to”
Quickly I looked up the groups and and provided the list, and got the response you already know. “We know that but what do those groups have access to?”

Of course this is a completely different question, so I set forth to write some code that would go through a file server and would tell me what the permissions where and at what level they changed. I purchased books and tried to call the API directly but was not able to create what I wanted and since nobody else could do it either the project just dropped off the radar.

Very recently I got another request. “We need to look through a directory structure to see where the “Domain Admins” group does not have full access. Users have been creating folders and adjusting permissions that we will need to fix without resetting everything in the tree.”

This time I started writing in PowerShell, I didn’t think I would say this but I wish PowerShell existed in the NT days. I didn’t need to fix anything just report, and within a couple of hours I had it.

 

The following script will go through directories and output 1 line for each folder or file it finds where the requested group does not have the access level defined UNLESS the file or folder is inheriting permissions from a higher level. The intent is that you should only need to make 1 change per line of output to correct the permissions, and even if there are thousands of files with incorrect permissions that inherit permission from a  parent.

You could have this fix things if you want using set-acl but I didn’t need to.

 

cls
# The intent of this script
# Loop recursively through files and folders starting at $target provided below)
# Look for the existance of an account or group in the ACL
# Check the access level for that user \ groupin the ACL
# Generate a report showing ONLY the places you would need to make a change
# to make sure the required account has the correct access on the entire tree.
# you could easily use set.acl to actually correct for issues (see note below) but I only needed a report.
#  File System Audit and the WABAC machine - www.blackops.ca - blog - for more info.
$target="\\Servername\sharename"
$GroupToCheck="Domain\User"
$AccessExpected="FullControl"
Function CheckAll {
# This function starts at the folder provided above
param([string]$rootfolder)
CheckACL $rootfolder
#Check the current folder ACL's
$items = Get-ChildItem -Path $rootfolder
If ($items)
# The above IF traps for a condition of a folder with no files in it.
{
foreach ($item in $items)
{
if ($item.Attributes -ne "Directory")
{
# for each file (note the -NE) check the ACL for the file
checkacl $item.fullName
}
}
foreach ($item in $items)
{
if ($item.Attributes -eq "Directory")
{
# for each directory (note the -EQ this time) Recurse back into this function with the directory as the new root folder
CheckAll $item.fullName
}
}
}
}
Function CheckACL {
param([string]$target)
$Check=$false
$inheritON=$false
$acl=Get-Acl $target # this reads the ACL
if ($acl.AreAccessRulesProtected)
# if AreAccessRulesProtected is TRUE then that means that a folder is NOT inheriting it's permissions
# if a folder is inheriting then it will NOT appear in the output
{
foreach ($ACLDetail in $acl.Access)
{
#This loops through each user \ access level and checks to see if access levels are explicit or inherited.
if ($acldetail.IsInherited) {$inheritON=$true} #inheritance check if inhereted file \ folder will NOT appear in report
if ($acldetail.IdentityReference -eq $GroupToCheck) #This checks each user for the one we are looking for (provided at the top of the script
{
if ($acldetail.FileSystemRights -eq $AccessExpected) #This checks to see if the permissions are correct
{
$Check = $true
}
else
{
#If the permissions are not correct output the full path\name and the current access level
Write-Host -NoNewline $Target
Write-Host -NoNewline "`t" #tab
Write-Host $acldetail.FileSystemRights
# If you want to fix the issue you would need a set_acl here and once more below
}
}
}
if (!$Check -and !$inheritON) #if the expected user is not found AND there is no inheritance set then log the file\folder
{
Write-Host -NoNewline $target
write-host -NoNewline "`t" #tab
write-host -NoNewline $GroupToCheck
write-host " Missing"
# If you want to fix the issue you would need a set_acl here and once more above
}
}
}
"begin"
checkall $target
"end"

And yes I am old enough to remember the WABAC Machine although not quite old enough to see the episodes as they aired for the first time.

 

 

Well it didn’t take long to go from report only to please fix it, so I have another script. If you want the script to add the required access for the defined group use the script below. I actually found set-acl harder to use than I expected and remember you see different statements for each condition

ADD to ACL for folder
Modify ACL for folder
ADD to ACL for file
Modify ACL for file

 

cls
# The intent of this script
# Loop recursively through files and folders starting at $target provided below)
# Look for the existance of an account or group in the ACL
# Check the access level for that user \ groupin the ACL
# Generate a report showing ONLY the places you would need to make a change
# to make sure the required account has the correct access on the entire tree.
# for more info s@blackops.ca
# THIS VERSION WILL CORRECT ISSUES. 
$target="C:\Temp"
$GroupToCheck="BUILTIN\Administrators"
$AccessExpected="FullControl"
#$DesiredPermission = $GroupToCheck,$AccessExpected,"ContainerInherit, ObjectInherit","NoPropagateInherit","Allow"
$person = [System.Security.Principal.NTAccount]"$GroupToCheck"
$access = [System.Security.AccessControl.FileSystemRights]"$AccessExpected"
$Folderinheritance = [System.Security.AccessControl.InheritanceFlags]“ContainerInherit, ObjectInherit”
$Fileinheritance = [System.Security.AccessControl.InheritanceFlags]“None”
$propagation = [System.Security.AccessControl.PropagationFlags]“NoPropagateInherit”
$type = [System.Security.AccessControl.AccessControlType]“Allow”
$FolderaccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($person,$access,$Folderinheritance,$propagation,$type)
$FileaccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($person,$access,$type)
$AccessModification = New-Object system.security.AccessControl.AccessControlModification
$AccessModification.value__ = 2
$Modification = $False
Function CheckAll {
# This function starts at the folder provided above
param([string]$rootfolder)
CheckACL $rootfolder
#Check the current folder ACL's
$items = Get-ChildItem -Path $rootfolder
	If ($items)
	# The above IF traps for a condition of a folder with no files in it.
	{
			foreach ($item in $items)
			{
			if ($item.Attributes -ne "Directory")
		       {
			   		 # for each file (note the -NE) check the ACL for the file
		             checkacl $item.fullName
		       }
		 	}
			foreach ($item in $items)
			{
			if ($item.Attributes -eq "Directory")
		       {
			   		# for each directory (note the -EQ this time) Recurse back into this function with the directory as the new root folder
		             CheckAll $item.fullName
		       }
		 	}
		}
}
Function CheckACL {
param([string]$target)
$Check=$false
$inheritON=$false
$acl=Get-Acl $target # this reads the ACL
if ($acl.AreAccessRulesProtected)
# if AreAccessRulesProtected is TRUE then that means that a folder is NOT inheriting it's permissions
# if a folder is inheriting then it will NOT appear in the output
	{
	foreach ($ACLDetail in $acl.Access)
		{
		#This loops through each user \ access level and checks to see if access levels are explicit or inherited.
		if ($acldetail.IsInherited) {$inheritON=$true} #inheritance check if inhereted file \ folder will NOT appear in report
		if ($acldetail.IdentityReference -eq $GroupToCheck) #This checks each user for the one we are looking for (provided at the top of the script
			{
			if ($acldetail.FileSystemRights -eq $AccessExpected) #This checks to see if the permissions are correct
				{
				$Check = $true
				}
			else
				{
				#If the permissions are not correct output the full path\name and the current access level
				Write-Host -NoNewline $Target
				Write-Host -NoNewline "`t" #tab
				Write-Host  $acldetail.FileSystemRights
				If (Test-Path $target -pathtype container)
					{
					# If you want to fix the issue you would need a set_acl here and once more below
					$acl.ModifyAccessRule($AccessModification, $FolderaccessRule, [ref]$Modification) |Out-Null
					Set-Acl $target -AclObject $acl
					$Check = $true
					}
				Else
					{
					$acl.ModifyAccessRule($AccessModification, $FileaccessRule, [ref]$Modification) |Out-Null
					Set-Acl $target -AclObject $acl
					$Check = $true 
					}
				}
			}
		}
		if (!$Check -and !$inheritON) #if the expected user is not found AND there is no inheritance set then log the file\folder
			{
			Write-Host -NoNewline $target
			write-host -NoNewline "`t" #tab
			write-host -NoNewline $GroupToCheck
			write-host  " Missing"
			if (Test-Path $target -pathtype container)
				{
				$acl.AddAccessRule($FolderaccessRule)
				Set-Acl $target -AclObject $acl
				# If you want to fix the issue you would need a set_acl here and once more above
				}
			Else
				{
				$acl.AddAccessRule($FileaccessRule)
				Set-Acl $target -AclObject $acl
				}
			}
	}
}
"begin"
checkall $target
"end"

Powershell – Rename Local Admin Account OR Ecuritysay Oughthray Obscurityway

This PowerShell script looks for the local Administrator account (as a member of the local Administrators group) and renames it. Then it creates a bogus local administrator account with a 32 character password that next expires and is not a member of any groups.

Likely this will only catch your local admins who have not had their morning coffee yet, but you can audit and know that any attempt to logon to Administrator is bad.

This script renames the Administrator to Admin (not very creative yes, if you don’t like that change $NewAdminName to something better. I suggest Ted.

The script takes input from a file c:\servers.csv that has a header of “ComputerName” and then one per line the list of server names you want to check and adjust.

If someone really wants more explanation post a comment.

Thanks to the creator of the password generator (Adam Bell) but I wish I noticed the comment about the break sooner. I have they adjusted code in my script.

function create-complexpassword
 {
 #  *** Unable to generate complex password less then 5 chars ***
 #  ASCII data taken from http://msdn2.microsoft.com/en-us/library/60ecse8t(VS.80).aspx
  
 Param (
   [int]$PassLength
   )
  
 #  Let's work out where our 3 complex characters will be inserted in the password...
   [int]$mark = ($PassLength/3)
   $ComplexChar = @("marker") 
  $ComplexChar[0] = $mark
   $ComplexChar = $ComplexChar+($ComplexChar[0] + $mark)
   $ComplexChar = $ComplexChar+(($ComplexChar[1] + $mark) -1)
  
   $Password = $null
   $rnd = new-object random
  
 #  "i" is our counter while we make the password, one char at a time.
   $i = $Password.length
   do 
    {
       switch ($Password.length)
         {
           $ComplexChar[0]
             {
               # Make this character a Numeric
               $password = $password+([char]($rnd.next(48,57)))
             
             }
           
           $ComplexChar[1]
             {
               # Make this character a LowerAlpha
               $password = $password+([char]($rnd.next(65,90)))
             }
           
           $ComplexChar[2]
             {
               # Make this character a Upper Alpha
               $password = $password+([char]($rnd.next(97,122)))
             }
             
           default
             {
               # In case this is used in a DCPromo answer files, theres a few chars to 
              # avoid: Ampersand, Less than, double quote and back slash
               $NextChar = $rnd.next(33,123)
               
               switch ($nextChar)
                 {
                   34 {break}
                   38 {break}
                   60 {break}
                   92 {break}
                   default 
                    {
                       $Password = $Password+([char]$nextChar)
                   
                     }                
                 }
             }
         }
       $i++
	    } 
  Until ($Password.length -eq $PassLength)
   return $Password
   }
cls
$NewAdminName="Admin"
$InputServers = Import-Csv “c:\servers.csv”
$Servercount = 1
$ServerCountTotal = $InputServers.Count
$InputServers | ForEach-Object {
			$_.computername
			$computer=$null
			$Group=$null
			$LocalAdministrators=$null
			$Administrator=$null
			$Admin=$null
			$PasswordString=$null
			$computer = [ADSI]("WinNT://" + $_.computername + ",computer")
			$Group = $computer.psbase.children.find("Administrators")
			$LocalAdministrators= $Group.psbase.invoke("Members") | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
			$Admin = $computer.psbase.children.find($NewAdminName) 
			$Administrator = $computer.psbase.children.find("Administrator") 
			$PasswordString=create-complexpassword(32)
			 If ($Administrator) 
			 {
			  if (($LocalAdministrators -contains "Administrator" ) -and (!$Admin))
			  	{
				$Administrator.Rename($NewAdminName)
				Start-Sleep -Seconds 1
				$computer=$null
				$Group=$null
				$LocalAdministrators=$null
				$Administrator=$null
				$Admin=$null			
				$computer = [ADSI]("WinNT://" + $_.computername + ",computer")
				$Group = $computer.psbase.children.find("Administrators")
				$LocalAdministrators=$null
				$LocalAdministrators= $Group.psbase.invoke("Members") | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
				$Admin = $computer.psbase.children.find($NewAdminName)
				$Administrator=$null
				$Administrator = $computer.psbase.children.find("Administrator") 
				}
			 }
			 If (!$Administrator)
			 	{
				$objUser = $Computer.Create("User","Administrator")
			 	$objUser.setpassword($passwordstring)
				$objUser.SetInfo()
				$objUser.userflags = $objUser.userflags + 65536 # flag - never expires 
			 	$objUser.SetInfo()
				$objUser.Description="Built-in account for administering the computer/domain"
			 	$objUser.SetInfo()
				$objUser.FullName=""
			 	$objUser.SetInfo()
				}
 }

Powershell – Add users to local groups OR Sounds like a job for group policies

Today I needed to add a couple of AD users to the local administrators group on a number of servers….. and I am not allowed to use a GPO to do it.

Not wanting to manually connect to every machine I used the following powershell script

cls

$InputServers = Import-Csv “c:\servers.csv”

$InputUsers = Import-Csv “c:\users.csv”

$Servercount = 1

$ServerCountTotal = $InputServers.Count

$InputServers | ForEach-Object {

$ServerTemp=$_.ComputerName

“Starting ” + $Servercount + ” of ” + $Servercounttotal + ” : ” + $ServerTemp

$InputUsers | ForEach-Object {

$objUser = [ADSI](“WinNT://” + $_.Username )

$objGroup = [ADSI](“WinNT://” + $ServerTemp +”/Administrators”)

$objGroup.PSBase.Invoke(“Add”,$objUser.PSBase.Path)

}

“Complete ” + $Servercount + ” of ” + $Servercounttotal + ” : ” + $ServerTemp

$Servercount++

}

“Script Complete”

 

The script looks for 2 CSV files in the root of c:\

Servers.csv has a header of “ComputerName” and then a list of servers you want to affect one per line.

Users.csv has a header of “UserName” and then a list of user names (OLD FORMAT) Domain/User one per line.

 

Enjoy

 

SCOM 2007 R2 Automatic Alert Closing or Death by Brackets

Issue:

You have informational alerts, or any other alerts in the SCOM console that you want to have, but not stack up forever.

Solution:

Power shell, again I am far from a power shell expert, in fact this might be the first script I have created that is more than just calling an existing command-let.

for those of you who don’t care, here is a line that will resolve informational alerts more than 12 hours old. (run it from Operations Manager Shell typically  C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe -PSConsoleFile Microsoft.EnterpriseManagement.OperationsManager.ClientShell.Console.psc1 -NoExit .\Microsoft.EnterpriseManagement.OperationsManager.ClientShell.Startup.ps1)

get-alert -criteria “Severity = ‘0’ AND ResolutionState = ‘0’ AND LastModified <= ‘$((((Get-Date).ToUniversalTime())).addhours(-12))'”|resolve-alert| out-null

For those who want to know how it works , or for me once I forgot

 Get-Alert is a SCOM command-let (try get-help get-alert) for all the details.

-criteria allows us to filter based on whatever we want.

Severity = ‘0’  This is a zero just in case you are wondering and

Severity 0 = informational

Severity 1 = Warning

Severity 2 = Error

ResolutionState = ‘0’ again a zero and means

ResolutionState = ‘0’ is New

ResolutionState = ‘255’ Closed

Anything in the middle would be things you configured as custom resolution states


and now  the one that took all the effort

LastModified is when the alert was last modified, well duh you say and I agree but now for the hard part. This is logged in UTC so it won’t match with what you see in the console so we need to feed it a UTC time 12 hours in the past and for that we need more brackets that I ever figured.

$(Get-Date) processes the get-date command-let and passes a date that looks like this “Sunday, February 07, 2010 1:30:00 PM”

$((Get-Date).ToUniversalTime()) Takes the date from above and converts it to UTC based on your time zone offset, resulting in “Sunday, February 07, 2010 9:30:00 PM”

$((((Get-Date).ToUniversalTime())).addhours(-12)) takes the date from above and subtracts 12 hours giving “Sunday, February 07, 2010 9:30:00 AM”

By the magic of power shell this is changed into something more like ‘2/7/2010 9:30: AM’ and for that magic I am eternally grateful as I always hated date format issues in scripting (yea powershell)

Now power shell has gathered a lost of all the alerts we want to clear and we simply pipe that to resolve-alert

and pipe the output from they whole line to out-null so we don’t get any output.


Scheduling the Task

I never imagined that it would take more time and lines of code to schedule this script than it did to create.

Normally you could just run

The script as saved on the local drive as ClearInfo.ps1

$RMSFQDN=”FQDN of your RMS
Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client
New-PSDrive -Name: Monitoring -PSProvider OperationsManagerMonitoring -Root: \
cd monitoring:\
New-ManagementGroupConnection $RMSFQDN
cd $RMSFQDN
$pf = (gc Env:\ProgramFiles)
cd “$pf\System Center Operations Manager 2007”
.\Microsoft.EnterpriseManagement.OperationsManager.ClientShell.Functions.ps1;
Start-OperationsManagerClientShell -ManagementServerName: $RMSFQDN -PersistConnection: $true -Interactive: $true;
get-alert -criteria “Severity = ‘0’ AND ResolutionState = ‘0’ AND LastModified >= ‘$((((Get-Date).ToUniversalTime())).addhours(-12))'”|resolve-alert| out-null

Then simply call something like C:\WINDOWS\system32\WINDOW~2\v1.0\powershell.exe C:\scripts\ClearInfo.ps1 from task manager.

You may want to have a look at http://technet.microsoft.com/en-us/library/ee176949.aspx