Report on SIDHistory

On December 4, 2011, in PowerShell, by Scott Garrett

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 = "(&(|(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

}
}
Tagged with:
 

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 it’s 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 = "(&(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
#Warning this script will check every object in every group the user is a member of recursively
#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 euther 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.member) # for each sub-group do the following
{
$member = [ADSI]("LDAP://"+ $x) #lookup the member in AD it could be a user or group or contact etc
#This is just a list of DN's we don't have a way of knowing for sure if it's a group or a user without binding to AD and checking the object type
#If you have groups with thousands of members this can take a long time and may hammer your DC's in the process - Don't say I didn't warn you
If ($member.SchemaClassName -eq "group") #we don't care if the member is not a group
{
$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 yo uenter a good username
do
{
$SAMAccountName=$null
$SAMAccountName = Read-Host $PromptMessage

if ($SAMAccountName) #if there was ANY input in the box
{
$searcher.Filter = "(&(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 uppate the message prompt so the user knows somethign 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 operastor

$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"
Tagged with:
 

‘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="\Servernamesharename"
$GroupToCheck="DomainUser"
$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 pathname 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 filefolder
{
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="BUILTINAdministrators"
$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 pathname 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 filefolder
			{
			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"
Tagged with:
 

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()
				}
 }
Tagged with:
 

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

 

Tagged with:
 

A SQL Function : Split or Reinventing the wheel

On July 28, 2011, in Uncategorized, by Scott Garrett

It has been far too long since my last post.

Lately I have been working on some operational tasks that include a log of data gathering. I am using powershell and sql for this and thus the next few posts are likely to move in this direction.

One of the first few things I found was a complete lack of a Split function in SQL. I have instances where I receive a delimited string from an external process and need to handle it for use in SQL.

I found the following function using XML and am reposting it here for future reference.  Thanks to http://blog.codelab.co.nz/2009/07/29/split-function-for-t-sql-using-xml/ for the code.

You simply call the function with 2 strings, the seperator and the string you want to split, and out pops a one column table with your results in each row.

select * from split (‘~’,’Strange things are~a foot at the circle-K‘)

RESULTS

val
Strange things are
a foot at the circle-K

CREATE FUNCTION [dbo].[Split](@sep VARCHAR(32), @s VARCHAR(MAX))

RETURNS @t  TABLE

(val VARCHAR(MAX))

AS

BEGIN

DECLARE @xml XML

SET @XML =N’<root><r>’+REPLACE(@s, @sep,’</r><r>’)+’</r></root>’

INSERTINTO @t(val)

SELECT r.value(‘.’,'VARCHAR(MAX)’)as Item

FROM @xml.nodes(‘//root/r’)AS RECORDS(r)

RETURN

END

 

Problem :

You go to create an email address and get the error “This e-mail address already exists in this organization”

so you search and find http://support.microsoft.com/kb/280765 and it does not help at all.

The Solution :

Active Directory Users and Computers utility

Right click on the domain, choose Find.

 In the Find dropdown list, choose “Custom Search” and  “Entire Directory”.

 Click the Advanced property page and enter the following LDAP query

proxyaddresses=smtp:smtp@domain.com

Click Find and you should find the object that already has that SMTP address.

Now you know what object and probably don’t have a problem if you are really in a bind right click, exchange tasks “Remove Exchange Attributes”


Tagged with:
 

If you have made your way through Part 1 then you have written your management pack complete with your own custom discovery and imported it into SCOM.  Once you have ensured that it is discovering only the devices you wish to manage in this pack it is time to begin writing the monitors and rules that will apply to the detected devices.   As was mentioned in Part 1a program such as MIB Browser can be very handy in assisting with sorting through all of the OID’s and the healthy values which correspond with each individual OID. 

Creating an SNMP Get Monitor in SCOM 2007 R2

I find the easiest way to create a new monitor or rule is to start with the System Center Operations Manager Console. I will admit it’s not the best and does not give you many of the options you probably want but I find it’s the easiest way to get the XML started, and then edit it to get exactly what we want after the fact.

We will start in the management console, Authoring tab, expand Management Pack Objects and right click on Monitors, Select Create a Monitor \ Unit Monitor.

From within SCOM click on the Authoring tab and then right click on Monitors which is listed beneath Management Pack Objects.  At this point we would like to choose Create a Monitor - Unit Monitor, once this has been picked you will see the following screen:

 First we will create a simple expression Get Monitor and later we will deal with TRAPS, so we pick SNMP – Probe Based Detection – Simple Event Detection – Event Monitor……

Be sure to create this in the management pack you created for the discovery of the object.

Now we have to name our monitor, Select a target (You are looking for the device type you defined in part one and you may have to click the “View all targets” radio button for it to appear) and add a parent monitor (this defines where in the health view tree your new monitor will appear)

Personally I always use the discovery community string but you could use something custom if you want. The frequency is how often you want the monitor to poll the device and the object identifier or OID. This is the bit this will be used in the SNMP get call I find it works most reliably if you don’t have a leading period.

We need to create an expression what causes an alarm. I will keep the expressions simple so you can get a feel for one that works. Click the +Insert at the top and you are presented with 3 fields.  The first field that appears parameter name is the magic field.

/DataItem/SnmpVarBinds/SnmpVarBind[1]/Value

This is the value you are going to compare it is based on the First SnmpProbe from the step before. I have read that if you have more than one SnmpProbe that the number in this case [1] is in reverse order so [1] is at the bottom and [2] would be just above it in the list. Personally I have only one OID providers right now so I don’t know. Let me know if you figure it out for sure.  The operator gives you a drop down of choices. I will get into it more below but thing about this one carefully. If you can use a simple equals or does not equal you can make things much easier. Think of it like this if a UPS battery charge of anything less than 100% is bad then use an expression like “/DataItem/SnmpVarBinds/SnmpVarBind[1]/Value - does not equal - 100″ instead of  “/DataItem/SnmpVarBinds/SnmpVarBind[1]/Value - less than - 100″ it will just save you a bunch of extra steps even if it is not quite as flexible.

 

Second SnmpProbe lets you pick an OID just like for the first SnmpProbe personally all the monitor I have so far use the same OID as in the first provider as I am watching for a single value to be either good or bad. The second expression is exactly the same as the first. If you want a monitor that will not recover (you have to manually reset the health state  I use something like “/DataItem/SnmpVarBinds/SnmpVarBind[1]/Value - does not match wildcard - *” since any GET will have some result it will never not match and this will never recover.

Configure health lets you decide how the device health will change when the monitor gets tripped. I use second event raised as healthy and first event raised as warning or critical depending on whats going on.

The last option is if you want to create an alert or not, up to you.

Not so simple expressions

So lets say you don’t want a simple equals or does not equal kind of expression. It’s there in the drop down so whats the big deal you say? Well the SCOM Console make what I consider a bad assumption when creating rules and monitors. All the datatypes are strings. so although “100″ does not equal “10″ produces a true result “100″ is greater than “10″ when the values are strings has no meaning. Fixing this is actually not so hard and you have 2 choices. If the next bit is clear to use go for manual xml editing, if that makes you nervous then hold on for the second option.

Option 1 : Advanced

Export your MP to XML and open it in your favorite xml editor.

Way at the bottom you will find an ElementID linked to the text label you assigned to the monitor. Use this ElementID to find your monitor or rule and alter as follows.  I have highlighted the 4 places you must change “String” to “Integer”. Save the file and re-import it into SCOM and your monitor should be working.

<UnitMonitor ID=”UIGeneratedMonitor38d2a38d163b4c1f971885f7ea686f16″ Accessibility=”Public” Enabled=”true” Target=”GEUPS.Single.Phase.Management.Pack.SNMPDevice” ParentMonitorID=”Health!System.Health.AvailabilityState” Remotable=”true” Priority=”Normal” TypeID=”Snmp!System.SnmpProbe.2SingleEvent2StateMonitorType” ConfirmDelivery=”false”>
        <Category>Custom</Category>
        <AlertSettings AlertMessage=”UIGeneratedMonitor38d2a38d163b4c1f971885f7ea686f16_AlertMessageResourceID”>
          <AlertOnState>Error</AlertOnState>
          <AutoResolve>true</AutoResolve>
          <AlertPriority>Normal</AlertPriority>
          <AlertSeverity>Error</AlertSeverity>
          <AlertParameters>
            <AlertParameter1>$Data/Context/SnmpVarBinds/SnmpVarBind[1]/Value$</AlertParameter1>
          </AlertParameters>
        </AlertSettings>
        <OperationalStates>
          <OperationalState ID=”UIGeneratedOpStateId8a4572649aec48b58c336b84182c464b” MonitorTypeStateID=”SecondEventRaised” HealthState=”Success” />
          <OperationalState ID=”UIGeneratedOpStateId2bcf307948d14f4a892a99088603714a” MonitorTypeStateID=”FirstEventRaised” HealthState=”Error” />
        </OperationalStates>
        <Configuration>
          <FirstInterval>60</FirstInterval>
          <FirstIsWriteAction>false</FirstIsWriteAction>
          <FirstIP>$Target/Property[Type="NetLib!Microsoft.SystemCenter.NetworkDevice"]/IPAddress$</FirstIP>
          <FirstCommunityString>$Target/Property[Type="NetLib!Microsoft.SystemCenter.NetworkDevice"]/CommunityString$</FirstCommunityString>
          <FirstVersion>$Target/Property[Type="NetLib!Microsoft.SystemCenter.NetworkDevice"]/Version$</FirstVersion>
          <FirstSnmpVarBinds>
            <SnmpVarBind>
              <OID>.1.3.6.1.2.1.33.1.2.4.0</OID>
              <Syntax>0</Syntax>
              <Value VariantType=”8″ />
            </SnmpVarBind>
          </FirstSnmpVarBinds>
          <FirstExpression>
            <SimpleExpression>
              <ValueExpression>
                <XPathQuery Type=”Integer“>/DataItem/SnmpVarBinds/SnmpVarBind[1]/Value</XPathQuery>
              </ValueExpression>
              <Operator>Less</Operator>
              <ValueExpression>
                <Value Type=”Integer“>96</Value>
              </ValueExpression>
            </SimpleExpression>
          </FirstExpression>
          <SecondInterval>60</SecondInterval>
          <SecondIsWriteAction>false</SecondIsWriteAction>
          <SecondIP>$Target/Property[Type="NetLib!Microsoft.SystemCenter.NetworkDevice"]/IPAddress$</SecondIP>
          <SecondCommunityString>$Target/Property[Type="NetLib!Microsoft.SystemCenter.NetworkDevice"]/CommunityString$</SecondCommunityString>
          <SecondVersion>$Target/Property[Type="NetLib!Microsoft.SystemCenter.NetworkDevice"]/Version$</SecondVersion>
          <SecondSnmpVarBinds>
            <SnmpVarBind>
              <OID>.1.3.6.1.2.1.33.1.2.4.0</OID>
              <Syntax>0</Syntax>
              <Value VariantType=”8″ />
            </SnmpVarBind>
          </SecondSnmpVarBinds>
          <SecondExpression>
            <SimpleExpression>
              <ValueExpression>
                <XPathQuery Type=”Integer“>/DataItem/SnmpVarBinds/SnmpVarBind[1]/Value</XPathQuery>
              </ValueExpression>
              <Operator>GreaterEqual</Operator>
              <ValueExpression>
                <Value Type=”Integer“>96</Value>
              </ValueExpression>
            </SimpleExpression>
          </SecondExpression>
        </Configuration>
      </UnitMonitor>

Option 2 : Easy

Export your management pack to XML then Using System Center Operations Manager 2007 R2 Authoring Console open it.  You may be asked for dependencies that are usually found in “C:\Program Files\System Center Operations Manager 2007″ but you can easily enough find *.mp

Once you find your monitor or rule of choice right click, properties. Configuration Tab.  Under each >>>XPathQuery and >>>Value you will see >>>@Type you need to change 4 Types to Integer.  You can see examples of the last two changed in the image below.

Then once you are finished just save the management pack and re-import it into SCOM and it should work.

 

Note: I am writing this in a somewhat sleep deprived state. I have not talked about rules at all but they are simpler than monitors so I hope it’s clear where the magic is. I will also thank David Allen for some blog posts that helped be although I can’t find them right now.  If things here are not clear or more detail is needed please comment or contact me and I will see what I can do.  

Part 1

Part 2

Part 3

Tagged with:
 

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

Tagged with:
 

SCOM 2007 R2 Console Command Line

On January 30, 2010, in SCOM 2007 R2, by Scott Garrett

Microsoft.MOM.UI.Console.Exe

Typically installed in C:\Program Files\System Center Operations Manager 2007

Microsoft.MOM.UI.Console.Exe /?

Command Line Syntax:

Microsoft.MOM.UI.Console.Exe {/Option[:Value]}

Option Description
/? Shows this help window
/ClearCache Clear the UI cache (this is the one that made me look for this)
/Server:<ServerName> Connect to the specified server
/ViewName:<ViewName> Display a view
/TaskName:<TaskName> Run a task
/TaskTarget:<ObjectId> Use in conjunction with /Task
/ManagementPack:<MpName> Use in conjunction with /TaskName and ViewName options

You can see it says you can find a complete list int he help but I have not found anything yet. Anyone else?

FYI – Don’t use /Clearcache when connected via RDP unless you know there are no others using the console at the time.

Also remember if you are trying to use viewname etc you should be using the internal name not the display name, something like this :

Microsoft.Mom.UI.Console.exe /viewname:System.Views.AlertView

Tagged with:
 
BlackOps home