narrow default width wide
colour style colour style colour style colour style

Fixing MMC errors

I’ve run into a number of issues with MMCs not working correctly or not working at all. Most of the time is has been caused by a corrupt MMC configuration file. All you have to do to fix this is delete or rename the old configuration file and the MMC will create a new one automatically.

 

The MMC configuration file is located in your Application Data folder and is associated with each User—not each computer.

 

Here is an example of the path to the config files:

 

\\server\share\UserSystem\username\Application Data\Microsoft\MMC (redirected App Data)

C:\Documents and Settings\mbroad\Application Data\Microsoft\MCC (local App Data)

 

Find the file that is appropriate for the MMC you are having issues with and delete or rename it. Most of the filenames are pretty obvious. Here are a few of them:

 

 

            GPMC             Group Policy

            DSA                Active Directory Users and Computers

            ADMGMT       Active Directory Management

            DSSITE           Active Directory Sites and Services

 

Here is the most common error you will see when you have a corrupt MMC:

MMC cannot open the file C:\WINDOWS\SYSTEM32\GPMC.MSC.

 

This may be because the file does not exist, is not an MMC console, or was created by a later version of MMC. This may also be because you do not have sufficient access rights to the file.

Purging redirected My Documents Recycle Bins from file servers

I'm being lazy on this one. This is from an e-mail I just sent out to the technical staff where I am currently consulting

---------------------------------------------------------------------

Here's another dangerous one. This is a simple two-line batch file that will delete all the files in the Recycle Bin in each of your user's UserHome folders. This was requested in last week's meeting.


for /d %%i in (\\s009-ns5\ssmr\userhome\a*) do @echo rem rd /q/s "%%i\my documents\recycler"
pause


The text in green needs to be the path to your UserHome folder.

The text in red is the wildcard for which user folders you want to process. This example will just process the folders that begin with the letter "A". You could put in "*" to run against all accounts or you could go the other route and put in a full username to just process a single folder (might be useful if you want to try it out).

The text in blue needs to be removed before the script will actually do anything. The echo means it will just show the command that it would run instead of actually running it. The 'rem' is just another thing I put in there to make sure no one accidentally ran it without understanding how it works.


Basically, it will run the "RD /S/Q" command on each 'recycler' folder in UserHome\%username%\My Documents.

Example of what it does:

rd /q/s "\\s009-ns5\ssmr\userhome\AEGANN\my documents\recycler"
rd /q/s "\\s009-ns5\ssmr\userhome\AMAGOS\my documents\recycler"
rd /q/s "\\s009-ns5\ssmr\userhome\AMANWA\my documents\recycler"
rd /q/s "\\s009-ns5\ssmr\userhome\APRESL\my documents\recycler"


You may or may not remember, but it is impossible to delete files from the Recycle Bin based on deletion date or anything like that. Unless you go through the GUI, it is an all or nothing affair. You can read the article at http://support.microsoft.com/kb/136517/EN-US/ if you want to understand why this is. Basically, if we selectively delete files, the index gets corrupt and none of the files will be visible in the GUI Recycle Bin (even though the file is actually still there). And, trust me, it is all but impossible to try to modify that index file manually. :)

Also, if you haven't ever used the FOR command, it can be your best friend for automating a repetitive task. It can be used in a lot of different ways other than processing multiple subfolders. (Here's where I referenced some samples they could review--I will put them at the bottom of the post) There isn't any great documentation explaining each sample but most of them are fairly easy to understand. But, the FOR command can be extremely powerful so please contact someone before using it if you aren't 100% sure what you are doing. Just running "FOR /?" from the command prompt will give you a lot of info on it (probably more info that you want to know...)

 

SAMPLES:

REM Exporting group membership
global "G046-Codefinder" ds >c:\codefinder.txt

REM TO process each entry in a text file
for /f %%i in (c:\codefinder.txt) do echo cacls \\%%i\c$\windows\hbowem32.ini /E /G Users:C

REM To return just the folder names for all subfolders in a folder
for /d %%i in (\\s009-ns5\ic\userhome\*.*) do @echo %%~ni

REM To process each subfolder in a specific folder (returns entire path)
for /d %%i in (\\s009-ns5\ic\userhome\*.*) do @echo %%i

REM Example of setting a "fake" delimiter so everything is assigned to %%i
for /f "tokens=1-20 delims=#" %%i in ('dir c:\*.*') do @echo %i%

REM Example that uses multiple variables based on the default delimiters (tab and space)
for /f "tokens=1-20" %i in ('dir c:\*.*') do @echo %i %j %k

REM Example that shows returning specific tokens using a custom delimiter
for /f "tokens=1-3 delims=, " %i in (c:\test.csv) do @echo %k   %j

Modifying the workstations that a user can logon to

Here's a multi-purpose script that helps to manage the workstation logon restrictions on user accounts. It can be used in two different ways:

1. To modify existing user accounts with workstation resrtictions to add additional systems they can logon to.

2. To restrict a new account to specific workstations.

The script allows you to setup "base" systems that will get added as well as giving you a prompt to specify additional systems they can logon to. This is handy because we define our Citrix servers as the base systems and allow the person running the script to put in the specific workstation(s) that a particular user logs on to directly.

And we use the functionality #1 above to update all of our accounts whenever we add a new Citrix server to the farm.

The only things you should need to modify are the lines in RED.

Option Explicit

'***Add the ability to apply this to a specific OU

Dim sObjType, sObjShortName
Dim strComputersToAdd, strBaseComputersToAdd, arrComputersToAdd
Dim strUserToModify, strUserDN
Dim sDomainADsPath
Dim blnProcessSingleUser


blnProcessSingleUser = False 'Set to True to do one user- False to process multiple users
       'Multiple users will only update accounts with existing restrictions
strUserToModify = "*" 
 'You can use wildcards for this if processing multiple users (* will do all)
 'Running multiple users will only update users that already have restrictions
strBaseComputersToAdd = "Citrix1,Citrix2,Citrix3,"
'strComputersToAdd = "Workstation1"

sDomainADsPath = "LDAP://" & ADRoot
sObjType = "user"

'Prompt for computer list to add if not already set
If strComputersToAdd = "" Then
 GetComputersToAdd
End If
'Prompt for user to modify if not already set-should mainly be used for processing single user
If strUserToModify = "" Then
 GetUserToModify
End If

If strBaseComputersToAdd "" Then strComputersToAdd = strBaseComputersToAdd & strComputersToAdd
arrComputersToAdd = Split(strComputersToAdd, ",")

If blnProcessSingleUser = True Then

 'WScript.Echo "2"
 strUserDN = GetObjDN(strUserToModify, sObjType)
 If strUserDN = "" Then
  WScript.Echo "Couldn't find user in AD"
  WScript.Quit
 End If 
 AddComputersToAllowedList(strUserDN) 'Run manually for one user
Else
 GetUsers 'Function to modify existing users with workstation restrictions
End If


WScript.Quit

'****************************************************************************
'****************************************************************************

Sub GetComputersToAdd

 'Inputbox if no ID already Set
 Dim strInputboxTitle, strInputboxMessage

 strInputboxTitle  = "Enter Computer list"
 strInputboxMessage  = "Enter the Computers to Add separated by a comma:"
 
 strComputersToAdd = InputBox(strInputboxMessage, strInputboxTitle)
 If strComputersToAdd = "" Then
  Wscript.Echo "No Computers entered - Process Cancelled"
  WScript.Quit
 End If

End Sub

Sub GetUserToModify

 'Inputbox if no ID already Set
 Dim strInputboxTitle, strInputboxMessage

 strInputboxTitle  = "Enter user"
 strInputboxMessage  = "Enter the user to modify"
 
 strUserToModify = InputBox(strInputboxMessage, strInputboxTitle)
 If strUserToModify = "" Then
  Wscript.Echo "No Computers entered - Process Cancelled"
  WScript.Quit
 End If

End Sub


Function GetUsers

 Dim sProperties, strCmdTxt
 Dim sUser, sPassword
 Dim oCon, oCmd, oRecordSet
 Dim intRecordCount


 Set oCon = CreateObject("ADODB.Connection")
 oCon.Provider = "ADsDSOObject"
 oCon.Open "ADProvider", sUser, sPassword
 Set oCmd = CreateObject("ADODB.Command")
 Set oCmd.ActiveConnection = oCon
 
 'sProperties = "name,ADsPath,description,mail,memberof"
 sProperties = "distinguishedname,userWorkstations"
 'strCmdTxt = ";(&(objectCategory=" & sObjType & ")(SamAccountName=" & sObjShortName & "));" & sProperties & ";subtree"
 'strCmdTxt = ";(&(objectCategory=" & sObjType & ")(SamAccountName=" & sObjShortName & "));" & sProperties & ";subtree"
 strCmdTxt = ";(&(objectCategory=" & sObjType & _
  ")(SamAccountName=" & strUserToModify & "));" & sProperties & ";subtree"
 WScript.Echo strCmdTxt
 oCmd.CommandText = strCmdTxt
 oCmd.Properties("Page Size") = 100
 On Error Resume Next
 Set oRecordSet = oCmd.Execute
 On Error goto 0
 
 intRecordCount = oRecordSet.RecordCount
 oRecordSet.MoveFirst
 While Not oRecordSet.EOF

  Dim strObjDN, arrObjDN, strDNPart, intDNPart, intOUDNEntry
  'Get the object's distinguishedname
  strObjDN = oRecordSet.Fields("distinguishedname")
  'WScript.Echo strObjDN
  On Error Resume Next
  Dim strWorkstations
  strWorkstations = ""
  strWorkstations = oRecordSet.Fields("userWorkstations")
  On Error Goto 0
  If strWorkstations "" Then
   'Run Function to add the computers in the list to the user object
   'Only run it if they already have workstation restrictions
   AddComputersToAllowedList(strObjDN)
  End If
  oRecordSet.MoveNext
 Wend

End Function


Function AddComputersToAllowedList(strUserDN)

 Dim objUser, strWorkSta, strOrigWorkSta, i
 On Error goto 0

 WScript.Echo "Modifying User: " & strUserDN

 ' Bind to user and retrieve userWorkstations.
 Set objUser = GetObject("LDAP://" & strUserDN)
 strWorkSta = objUser.userWorkstations
 strOrigWorkSta = strWorkSta
 
' If (strWorkSta = "") Then
'  strWorkSta = strAddComputers
' Else
  'loop through array of systems to add, check one at a time to see if it is already added
  For i = LBound(arrComputersToAdd) To UBound(arrComputersToAdd)
  
   'WScript.Echo "checking: " & arrComputersToAdd(i)
   If InStr(lcase(strWorkSta), lcase(arrComputersToAdd(i))) Then
   Else
    'Add the current system to the string
    If strWorkSta = "" Then
     strWorkSta = arrComputersToAdd(i)
    Else
     strWorkSta = strWorkSta & "," & arrComputersToAdd(i)
    End If
   End If
  Next
' End If
 
 WScript.Echo vbTab & "Original List: " & strOrigWorkSta
 WScript.Echo vbTab & "New List:      " & strWorkSta

 ' Update user and commit changes.
 objUser.Put "userWorkstations", strWorkSta
 objUser.SetInfo

End Function


Function ADRoot()

 Dim oRootDSE
 On Error Resume Next
 Set oRootDSE = GetObject("LDAP://RootDSE")
 If Err.Number 0  Then
  ADRoot = "DC=ZZ,DC=YY,DC=XX,DC=com"
 Else
  ADRoot = oRootDSE.Get("defaultNamingContext")
 End If
End Function

Function GetObjDN(sObjShortName, sObjType)
 'This function queries AD for a user by SAMAccountName and returns the distinguishedName for it
 '(DN is used for LDAP binds...)

 Dim sDomainADsPath, sProperties, strCmdTxt
 Dim sUser, sPassword
 Dim oCon, oCmd, oRecordSet
 Dim intRecordCount

 sDomainADsPath = "LDAP://" & ADRoot

 Set oCon = CreateObject("ADODB.Connection")
 oCon.Provider = "ADsDSOObject"
 oCon.Open "ADProvider", sUser, sPassword
 Set oCmd = CreateObject("ADODB.Command")
 Set oCmd.ActiveConnection = oCon
 
 'sProperties = "name,ADsPath,description,mail,memberof"
 sProperties = "distinguishedname"
 strCmdTxt = ";(&(objectCategory=" & sObjType & ")(SamAccountName=" & sObjShortName & "));" & sProperties & ";subtree"
 'WScript.Echo strCmdTxt
 oCmd.CommandText = strCmdTxt
 oCmd.Properties("Page Size") = 100
 On Error Resume Next
 Set oRecordSet = oCmd.Execute
 On Error goto 0
 
 intRecordCount = oRecordSet.RecordCount
 If intRecordCount = 1 Then
  oRecordSet.MoveFirst
  While Not oRecordSet.EOF
 
   Dim strObjDN, arrObjDN, strDNPart, intDNPart, intOUDNEntry
   'Get the object's distinguishedname
   strObjDN = oRecordSet.Fields("distinguishedname")
   oRecordSet.MoveNext
  Wend
  GetObjDN = strObjDN
 Else
  WScript.Echo "ERROR: Expected exactly 1 record from AD. Records received = " & oRecordSet.RecordCount
  'GetObjDN = False
 End If

End Function ' End of GetObjDN Function

Great site for "800" error codes you get in VB/VBS

I've ran across it before but it was handy again today so I figured I would share.

http://www.computerperformance.co.uk/Logon/code/index.htm

The error number that your code spits out probably won't be in hex format so you can either convert it to hex in your code or you can just put the decimal number into Calc (in scientific mode) and convert it to Hex. Make sure you make it a negative number in calc before you convert it if the error number is negative.

Converting the error number to hex in your code (soooo complicated) :)

wscript.echo hex(err.number)

Updated Storage State Monitoring script for MOM

I was just about to make some modifications to the default Storage State Monitoring script in MOM so we could set special thresholds for specific volumes but I figured I'd do a bit of Googling first. I came across the following scripts:

http://webpages.charter.net/justin.harter/MOM/Scripts/ModifiedStorageStateScriptv2.txt

http://www.gotdotnet.com/codegallery/codegallery.aspx?id=ada0df24-c103-42c0-9a70-3e805be003cd

The first URL is just some more modifications to the one listed at the second URL. They didn't make the exact modifications that I would make if I re-wrote it myself but it was pretty darn close and the script is working really well.

So, a big thanks to Garth and Gerald. You saved me a lot of hours of coding and testing!

Cleaning up MOM alerts

The company I am consulting at has had MOM installed for quite a while but no one has ever really "owned" it so alerts haven't always gotten processed like they should. I'm no MOM expert (fighting off the temptation to throw in a yo' momma comment here...) but I figured I would do some hacking around.

The first thing I noticed what that all of the servers were "Critical" in the status view but there weren't nearly enough alerts in the Alerts view for all of the server to be critical. After going through every menu 3 times to find a way to see if there were some special view filters (and creating a new custom view that still didn't show everything), I stumbled across the stupid clock/calendar in the upper-right corner.

Presto magic! I changed it from the default of 7-days to 1000-days and lo-and-behold, I have thousands of alerts. I wanted to acknowledge them all because I figured MOM would just regenerate them if they were still valid issues that need to be addressed. So, I right-click and do a Select-All so I can acknowledge them, but alas, there is no 'Select All' option. Surely that option is in the menu somewhere, right? Nope. Ok, I'll just select the first record and scroll to the bottom and do a Shift-Select. No dice there either. MOM popped up some stupid message about exceeding the limit for how many records could be selected at once. I went through the process of selecting the maximum number of records and acknowledging them about 10 times and (based on how far the scroll bar had adjusted) figured it would take me about 3 days to get them all acknowledged.

Now, being a programmer, I figure I can pull it off via scripting or SQL commands but I always like to see if there is a built-in mechanism first. I stumbled across the "Data Grooming" settings and it looked like adjusting them should cause the records to purge themselves automatically (at least anything older than 45 days). I set everything up to auto-resolve within 30 days and for all data older than 45 days to be groomed and let it run for a couple of days. Unfortunately, this didn't seem to work. I don't know if it just needs more time to run but I got impatient and decided to do things "My Way".

First off, let's open up the database and see how the data is stored. I checked a few tables to figure out how everything was cross-referenced (after dealing with the SMS Database I never expect things to be organized) but I was happily surprised to see that everything I needed to work with was in the table named "Alerts". I didn't run across this until after I had everything fixed but Microsoft has a great breakdown on the MOM database/object properties.

Long story short, after a bit of tinkering with manual SQL changes to individual records to make sure everything worked the way I thought it did, I ended up running the following script in Query Analyzer:

update alert SET ResolutionState = 255 where resolutionstate 255 and culprit = 'Citrix Metaframe'

Bascially, it just changes all of the records that haven't been acknowledged so that they are now acknowledged. (that's what 255 means-check the MS link above for a list of all possible values). I chose to also limit my changes to alerts that came from 'Citrix Metaframe' because they comprised about 95% of them. Apparently the MOM Citrix Management Pack alerts every time a printer mapping fails--which happens ALL OF THE TIME. Someone had shut it off because they were tired of getting e-mailed but obviously all of the MOM alerts were still lurking out there--hidden by the "7-day" filter that the Alerts view defaults to.

So, I learned a bit more about MOM and I am on my way to cleaning up a bunch of other issues with their MOM setup. I am down to a much more managable 150 active alerts that I need to review and determine how to handle.