Skip navigation

Category Archives: Technology

Discussion on any and all tech related items. New tech, tech usage, etc.

The 9 traits of a veteran Unix Admin:

  1. Bold
  2. Pretentious
  3. Obfuscated
  4. Lazy x4
  5. Minimalist
  6. Condescending
  7. Inquisitive
  8. Exclusive
  9. Unyielding

 

Now the original article from 2011 I read for this made those traits sound a lot better, but ultimately that’s about right.

These aren’t inherently bad traits for a systems admin (some are though), but they obviously require some polish before being acceptable in the workplace, but I thought this was worth sharing with the community at large.

 

So I haven’t written anything even mildly technical lately so I thought I’d show a small script I use to copy and restore my scripts and PowerShell profile across multiple machines.  It’s fairly straight forward.  It will backup my scripts directory from my desktop and PowerShell folder from my documents directory; and recreates the the folder structure in the directory the script is run from.

Conversely it will restore as well provided the content exists.  It is USER specific, so if you use multiple user id’s like I do, it’s important that you manage them accordingly.


Option Explicit
On Error Resume Next
Dim oWShell, oFSO, oNet
Dim strCurUser, strSourceDrive, strTargetDrive, strOption, strMsg
Set oWShell = CreateObject("Wscript.Shell")
Set oFSO 	= CreateObject("Scripting.Filesystemobject")
Set oNet 	= CreateObject("Wscript.Network")

strCurUser = oNet.UserName
strTargetDrive = Left(WScript.ScriptFullName, Len(WScript.ScriptFullName)- _
				Len(WScript.ScriptName)) & strCurUser
strSourceDrive = oWShell.ExpandEnvironmentStrings("%userprofile%")
strMsg = "Filecopy Summary: "& vbcrlf
strOption = LCase(InputBox("Restore|Backup"))

Select Case strOption
    Case "restore"	Call Restore()
    Case Else	Call Backup()
End Select

WScript.Echo strMsg
'-------------------
Function Backup()
	
Dim strScripts, strPShell
	strScripts = "\Desktop\Scripts" : strPShell = "\Documents\windowspowershell"

If Not CreateFolder(strTargetDrive & strScripts) Then
	strMsg = strMsg & "Failed Creating: "& strTargetDrive & strScripts & vbCrLf
Else
	strMsg = strMsg & "Created: " & strTargetDrive & strScripts & vbCrlf
End If

If Not CreateFolder(strTargetDrive & strPShell) Then
	strMsg = strMsg & "Failed Creating: "& strTargetDrive & strPShell & vbCrLf
Else
	strMsg = strMsg & "Created: " & strTargetDrive & strPShell & vbCrlf
End If
	
If Not CopyFolder(strSourceDrive & strScripts, strTargetDrive & strScripts) Then
	strMsg = strMsg & "Failed to Copy: " &  strSourceDrive & strScripts & vbCrLf
Else
	strMsg = strMsg & "Copied: " & strSourceDrive & strScripts & vbCrLf
End If

If Not CopyFolder(strSourceDrive & strPShell, strTargetDrive & strPShell) Then
	strMsg = strMsg & "Failed to Copy: " & strSourceDrive & strPShell & vbCrLf
Else
	strMsg = strMsg & "Copied: " & strSourceDrive & strPShell & vbCrLf
End If

End Function
'-------------------
Function Restore()

Dim strScripts, strPShell, temp1, temp2
	strScripts = "\Desktop\Scripts" : strPShell = "\Documents\windowspowershell"
temp1 = strSourceDrive : temp2 = strTargetDrive
strTargetDrive = temp1 : strSourceDrive = temp2

If Not CreateFolder(strTargetDrive & strScripts) Then
	strMsg = strMsg & "Failed Creating: "& strTargetDrive & strScripts & vbCrLf
Else
	strMsg = strMsg & "Created: " & strTargetDrive & strScripts & vbCrlf
End If

If Not CreateFolder(strTargetDrive & strPShell) Then
	strMsg = strMsg & "Failed Creating: "& strTargetDrive & strPShell & vbCrLf
Else
	strMsg = strMsg & "Created: " & strTargetDrive & strPShell & vbCrlf
End If
	
If Not CopyFolder(strSourceDrive & strScripts, strTargetDrive & strScripts) Then
	strMsg = strMsg & "Failed to Copy: " &  strSourceDrive & strScripts & vbCrLf
Else
	strMsg = strMsg & "Copied: " & strSourceDrive & strScripts & vbCrLf
End If

If Not CopyFolder(strSourceDrive & strPShell, strTargetDrive & strPShell) Then
	strMsg = strMsg & "Failed to Copy: " & strSourceDrive & strPShell & vbCrLf
Else
	strMsg = strMsg & "Copied: " & strSourceDrive & strPShell & vbCrLf
End If

End Function
'-----------
Function CreateFolder(strFolder)
On Error Resume Next
	Dim FolderArray, Folder, Path, booErr
	FolderArray = Split(strFolder,"\")
		For Each Folder In FolderArray
			If Path <> "" Then
				If Not oFSO.FolderExists(Path) Then
					oFSO.CreateFolder(Path)
				End If
			End If
				Path = Path & LCase(Folder) & "\"
			If Err.Number <> 0 Then
				booErr = True
			End If
		Next
		If booErr Then
			CreateFolder = False
		Else
			CreateFolder = True
		End If
End Function
'-----------
Function CopyFolder(strSource, strDest)
On Error Resume Next
	If oFSO.FolderExists(strDest) Then
		oFSO.DeleteFolder strDest, True
	End If
		oFSO.CopyFolder strSource, strDest, True

	If Err.Number <> 0 Then
		CopyFolder = False
	Else
		CopyFolder = True
	End If
End Function

There you go, a bit lacking on the sophistication side of things, but it gets the job done for me.  Feel free to modify the script for your own personal file backup needs, it can really help to streamline the process.

Is addictive…. link

But seriously, the game is absolutely a blast.  RPG meets Tower Defense meets Hack and Slash… I’d write more, but I’m going to go back and play.

So I hit up a friend of mine at work to write a quick write up and example of batch scripting; which he leverages heavily administratively, and here’s the result:

For me, batch is the quickest and easiest way to automate most tasks for server administration.  These tasks can be completed by leveraging built-in commands, resource kit executables, and other Microsoft/SysInternals type tools.  I would like to begin at the beginning, but if I had that much patience I would have learned a real scripting language, so I will start in the middle.

Step 1 is figuring out how to get information you need to manipulate out of the command line, here’s a good start:

http://technet.microsoft.com/en-us/library/cc778084(v=ws.10)
http://www.microsoft.com/en-us/download/details.aspx?id=17657
http://technet.microsoft.com/en-us/sysinternals/bb842062.aspx

Step 2 is formatting and passing that information to additional tools and/or parsing the results for review, (done by the batch itself).

Scenario:  You have a directory on a server containing many user profiles.  Under these profile directories is a ‘cache’ directory that you want to erase every night.  User profiles are create/deleted on a regular basis, so creating a static batch of RMDIR commands is not an option.

C:\Users>dir
Volume in drive C has no label.
Volume Serial Number is B043-A733

Directory of C:\Users

07/03/2012  07:20 AM    <DIR>          .
07/03/2012  07:20 AM    <DIR>          ..
07/03/2012  07:17 AM    <DIR>          Andy
07/03/2012  07:17 AM    <DIR>          Curtis
07/03/2012  07:17 AM    <DIR>          Daniel
07/03/2012  07:17 AM    <DIR>          Mark

You want to run the command: rmdir /s /q c:\users\<insert username>\cache, where the username is dynamically pulled from the current c:\users directory.  First, tweak your command with any available options/flags that give you an output that is easier to work with.  In this case, the /b (brief) flag for DIR will strip the output of any extra format.

C:\>dir c:\users /b
Andy
Curtis
Daniel
Mark

We will reference the output from this command as a single string variable per line, so removing header formatting and unnecessary columns is ideal.   For this script I’ll be using a FOR loop; there are several different types of FOR loops in DOS, though I use /F (filenameset) almost exclusively.  Typing FOR /? from a command prompt will give you the full list of options, but we will focus on the two that read from either a text file or a command output.  From the FOR /? help:

FOR /F [“options”] %variable IN (file-set) DO command [command-parameters]
FOR /F [“options”] %variable IN (‘command’) DO command [command-parameters]

The “options” I most commonly use are “tokens” and “delims”, to specify which column(s) to read from, and what character(s) delimit columns (blank space is the default delimiter).  We narrowed down the output to only one column, so “delims” will not be used in this example, and there is only one token available.

@echo off 
for /f “tokens=1” %%a in (‘dir c:\users /b’) do (rmdir /s /q c:\users\%%a\cache)

 

Lets take a closer look at each component of the for loop and what it is doing:

for /f

Perform an action for every line in a text output

“tokens=1”

Identify the first word on every line (tokens=2 would be the second word, etc)

%%a

Reference that first word of every line as %%a

in (‘dir c:\users /b’)

The text output we are reading from is the command ‘dir c:\users /b’

you can also read a text file instead of command output by removing the single quotes,

IE: for /f “tokens=1” %%a in (serverlist.txt)

do (actions)

action(s) to be performed for every line

Now this batch is ready to be added as a scheduled task and automatically delete the specified cache directories every night.  You can create the scheduled task via command line as well, but I don’t see much of a need for that in this case. As much as I enjoy automation and CLI, I typically avoid both when they are not the quickest way to get the job done.

Alright, so if you read my previous post for AD to DB then this post will make more sense.  However if you haven’t; then now would be a good time to… go ahead, I’ll wait.

Now one of the primary purposes I had for this data was to leverage it against my v_r_system view and determine which active assets are missing their client.  Well that’s fine and good, but I had been in the habit of taking that data and then performing a DNS check for the entries using a Powershell script written by Jason Sandys.  Originally I was using a vbscript I wrote to do so, but found his to be far more efficient since it made use for the DNS class in .Net.  But I digress, the end result is that I had quite a few additional steps to determine which machines were active, and ready for remediation either locally by desktop support, or by my remote repair tools.

So, what to do?  Why not query what I need, run the DNS check from a data set, then write the results back to my data warehouse?  This script requires a few things:

  1. Working, integrated credential for querying the SCCM DB; and permission to drop, insert, and create for the DB warehouse.
  2. Table within the data warehouse with the AD data provided with my AD to DB script to join against the SCCM DB data.
  3. Established linked servers between the two databases to perform the join (stored query in the script calls the join from the SCCM DB server, so the link is required there)

 

Now that we have that out of the way lets discuss the variables that need to be modified here.

  • $db – SCCM Database for the primary site server
  • $sqlsrvr – SCCM Database Server Name
  • $db2 – Data Warehouse DB
  • $sqlsrvr2 – Data Warehouse Server Name
  • $table – Data Warehouse AD Table

 

Alright, so there it is, now time for the code; which I want to apologize in advance for it’s wide column width (download):

#We expect errors for hosts we can't find, so running silently
	$ErrorActionPreference = "SilentlyContinue"
#Configuring connection and query variables for the sql client adapter
$db = "sms_abc" 					#sccm database
$sqlsrvr = "SCCMDBServer"  			#sccm db Server Name
$db2 = "DataWarehouse"				#Data Warehouse db
$sqlsrvr2 = "DataWarehouseServer"	#Data Warehouse Server Name
$table = "ADtablefromDatawarehouse"	#The table where AD data is stored
$sqlquery = "select sys.Name0 from $sqlsrvr.$db.dbo.v_r_system as sys join `
			$sqlsrvr2.$db2.dbo.$table as adlist on adlist.ad_machine = sys.name0`
			where DATEDIFF(d,passwordlastset,getdate()) <= 30 and Client0 = 0 or`
			DATEDIFF(d,passwordlastset,getdate()) <= 30 and Client0 is null"
#Performing the query and writing to a data set
$sqlcon = New-Object System.Data.SqlClient.SqlConnection("Data Source=$sqlsrvr;Integrated Security=SSPI;Initial Catalog=$db;")
	$cmd = New-Object System.Data.SqlClient.SqlCommand
		$cmd.CommandText = $SQLQUERY
			$cmd.Connection = $SQLCON
	$sqladapter = New-Object System.Data.SqlClient.SqlDataAdapter
		$sqladapter.SelectCommand = $CMD
			$DS = New-Object System.Data.DataSet; $DS.Tables.Add("SQLQuery")
	[Void]$sqladapter.Fill($DS.Tables["SQLQuery"])
$sqlcon.Close()
#Building new table in SQL for DNSQuery
$table = "NoClientDNSRecord"	
	$sqlcon = New-Object System.Data.SqlClient.SqlConnection("Data Source=$sqlsrvr2;Integrated Security=SSPI;Initial Catalog=$db2;")
$sqlcon.Open()
	$cmd = $sqlcon.CreateCommand()
		$cmd.CommandText = "drop table $table"
			[Void]$cmd.ExecuteNonQuery() 
		$cmd.CommandText = "create table $table (Name varchar(150) not null Primary key,IP varchar(25), Reverse varchar(150), status varchar(50))"
			[Void]$cmd.ExecuteNonQuery() 
#Performing a DNS query against each machine in our SQL data set
foreach($row in $DS.Tables["SQLQuery"].rows){
	$system = $row[0]
	$sys = New-Object PSObject
		$sysname = $system.ToLower().Trim()
			$sys | Add-Member -MemberType NoteProperty -Name Name -Value $sysname
	#Getting IP address for the host name
	$sys | Add-Member -MemberType NoteProperty -Name IP -Value "-"
		$sys.IP = [System.Net.DNS]::GetHostEntry($sysname).AddressList | select -First 1
			$firstOctet = ($sys.IP -split "[.]")[0].Trim()
	#Getting reverse address from dns for the host name
	$sys | Add-Member -MemberType NoteProperty -Name Reverse -Value "-"
		$sys.Reverse = [System.Net.DNS]::GetHostEntry($sys.IP).HostName | select -First 1
			$sys.Reverse = ($sys.Reverse -split "[.]")[0].ToLower().Trim()
				if ($sys.Reverse -eq $firstOctet){$sys.Reverse = "-"}
	#Writing a status for the entry based on  name and reverse lookups.	
	$sys | Add-Member -MemberType NoteProperty -Name Status -Value "-"
		if		($sys.IP -eq "-")			`
		{$sys.Status = "Could not Resolve IP"}
		elseif	($sys.Reverse -eq "-")		`
		{$sys.Status = "IP Address not found in reverse zone"}
		elseif	($sys.Name -ne $sys.Reverse)`
		{$sys.Status = "IP registered to another system"}
		else								`
		{$sys.Status = "OK"}
#Writing values to SQL
$cmd.CommandText = "insert $table values ('$($sys.name)','$($sys.ip)','$($sys.reverse)','$($sys.status)')"; [Void]$cmd.ExecuteNonQuery()}
$sqlcon.close()

 

I’d recommend using PowerGUI for reviewing/modifying this code as it’s by far the best (free) powershell editor available.

Until next time, have a good one!

So I had another problem, and one I know I’m not alone in…

I’ve got clients that are failing, won’t install, have corrupt wmi repositories, and obviously I can’t deploy a repair solution to them so what do we do?

Well like most people in an enterprise environment (or smaller I’m sure) we maintain a logon script, and considering that this script will run come hail or high water it is clearly the best possible automated resolution point possible.

There have been some really great log on health check scripts written by a lot of great people in the MyITForum community, and having used two of the more popular ones I’ve decided there are some great things in both of them, but in one case more than what I need; and the other just short of my needs.

So what do we do?  Simple, write our own; which you can download from a link at the bottom of this post.

So lets first look at what it takes to run this script:

CmLogonFrameWork.wsf + config.xml

At run time you can configure 4 override settings:

  • /log:”c:\windows”

This will write the logfile to the windows directory.

  • /config:”somefilesomewhere.xml”

This will load the specific config file you need, useful if you have multiple configurations for multiple machine/user types.

  • /events

This will force the script to write events to the event log regardless of how it’s defined in the config.xml

  • /debug

This will force the script to error verbosely, which will become useful if you are going to extend the code and need to see the error output.

So what does the script do?  Well I’ve put my horrible Visio skills to work to give you the obfuscated flowchart that might very well make you want to claw your face off (included in the script download).  However I’ll give you a quick overview of the intended functionality to save you from all the head scratching and the “what was this guy smoking” statements you might have as you read it.

It’s best to think about this script in 5 blocks:

  1. Initialization
  2. System Check
  3. SCCM Check
  4. SCCM Configuration
  5. Close Out

 

Initialization:

This block is fairly straight forward.  We gather arguments, enforce a cscript execution (for cleanliness more than anything), generate our first log entry, load the configuration file, and write a LastRun entry to HKLM\Software\SccmHealth for tracking of changes and repair mechanisms later.

The only potential repair function during this phase would be to the XMLDom (and all the script will attempt to do is register the component).  If OS version checks fail, or the XML file cannot be read then the script will close with an error in the log.

System Check:

The system check is also fairly straight forward.  We first check the admin account if one is configured, we check the admin shares, we check the list of services defined in the config, and finally we verify that the root/cimv2 is accessible in WMI.

Repairs that would occur in this phase are the enabling of admin shares via service and registry values.  Services are configured per the config file if they are not presently in that state. And finally if it’s enabled, an attempted wmi rebuild would be performed if wmi fails its check (there are two distinct repair types determined by the version of windows).

SCCM Check:

Now things get a bit more confusing, but I’ll keep it high level since I have a bit of an election system in the script on what repair method is the best option.

Check the CM namespace in WMI, then the service, then the executable, and finally the setup files.  If all 4 fail or 3 of 4 then a standard installation will occur.  If only the namespace fails then a reinstallation is attempted provided that option is allowed, if not then a wmi repair is attempted if it is allowed.  There are other checks that manipulate the outcome such as your configuration options obviously and when the last repair date was attempted on the asset.

SCCM Configuration:

This section is very straightforward.

Check the Site code, and check the cache size, if they don’t match the config, we change them to match.

Close Out:

As our final phase we load any additional scripts identified in the config file, generate our System Check, SCCM Check, and SCCM Configuration check report card.  If remote logging is enabled we write the final output there as well, and finally we deconstruct our objects and  terminate the script with or without errors depending on the outcome of course.

Tips for Extending:

The two custom objects being used here in this script are cls_Logging and cls_Dict which are instantiated as Logging and Config respectively.

A list of these classes and how they work can be found here (logging) and here (config).

I’ve also included a test.vbs that extends the script to give you an idea of some of very basic examples of extended functionality.  I would recommend you avoid adding anything to the primary script, but instead extend it all as a separate script and have it performed at the end of the script, unless of course it’s an entirely new object that you wish to build into this tool then by all means have at it.

 

If you have any questions, suggestions, or if you find bugs please comment so I can answer them or repair them, thanks.

Download:

Version 2.1.3

Alright, so Active Directory Asset Management.  What is it?

Well a, not so, unique problem facing every enterprise large or small, is asset management.

Now asset management is not a one dimensional issue, there are requirements and facets to it that shift depending on the person you ask.  From an engineering and maintenance standpoint at my company we have needs of tying important information as closely to the asset as possible, yet removed enough that it’s accessible even when the asset is not.  So what does that mean?

I want to have information tied to the machine but not dependent on the machine.  Something that contains properties that can be modified by the machine, yet retrievable apart from the machine.  Well that’s a relatively simple solution, LDAP, or Active Directory as the case may be.

In this case, my co-worker David Renfrow has opted to utilize the AD Computer Object Description property to store the strings that uniquely identifies our asset.  At present the tool is used to search for specific values in objects, to get/set the string values, and lastly to export findings from the search to a csv report bundled into a single self contained HTA.

image

For the next release I intend to implement error handling, and a potential DB write out function as well as domain search base targeting. At present it only pulls from the Domain of the querying asset, so in a multi domain forest that can be a problem.

Current Version Code Below:


<html>
	<head>
		<title>Active Directory Asset Manager (ADAM)</title>
<!--<description>
	Tool:		Active Directory Asset Manager
	
	Authors: 	David Renfrow, Daniel Belcher 
	
	Tool Info: 	The tools purpose is to retrieve, query, and 
				set system specific information	to a string in 
				the ADO Description property.  This string is 
				specially formatted	to retrieve in a specific way:
				
		<Server>;<Sixdot>;<Purpose>;<Location>;<Domain>;<SLA>;
	<SrvContact>;<SrvManager>;<SGPrimary>;<SGBackup>;<DBAPrimary>;<DBABackup>
				
</description>-->
	<HTA:APPLICATION 
		ID="ADAM" 
		APPLICATIONNAME="ADAM"
		BORDER="thin"
		SINGLEINSTANCE="yes"
	>

<SCRIPT LANGUAGE="VBScript">
'-----------Application Initialization-----------------------------------------
'On Error Resume Next
Dim oConn, oCmd, item, oComputer, strDomain, Dict

Sub Window_onLoad
	window.resizeTo 625,725
	
	Set oConn = CreateObject("ADODB.Connection")
		oConn.Provider = "ADsDSOObject"
	Set oCmd =  CreateObject("ADODB.Command")
	Set Dict = New cls_Dict

	strDomain = Dict.DistinguishedDomainName
	
	
End Sub 
'----------------Controls------------------------------------------------------
Sub getproc()
	On Error Resume Next

	strComputer = ServerName.Value
	ADLookUp(strComputer)
	Description = oComputer.Get("Description")
	If Err.Number <> 0 Then
		Err.Clear
		Description = ";;;;;;;;;;;"
	End If
	strOut = Split(Description,";")

	DataArea.innerHtml =	"<table border=""""1"""">" & "<tr>" & _
		"<td>Server</td>"&"<td>"&UCase(strComputer)& "</td>" & "</tr><tr>" & _
		"<td>SixDot</td><td>"&strOut(1) & "</td>" & "</tr><tr>" & _
		"<td>Purpose</td><td>"&strOut(2) & "</td>" & "</tr><tr>" & _
		"<td>Location</td><td>"&strOut(3) & "</td>" & "</tr><tr>" & _
		"<td>Domain</td><td>"&strOut(4) & "</td>" & "</tr><tr>" & _
		"<td>SLA</td><td>"&strOut(5) & "</td>" & "</tr><tr>" & _
		"<td>Srv Contact</td><td>"&strOut(6) & "</td>" & "</tr><tr>" & _
		"<td>Srv Manager</td><td>"&strOut(7) & "</td>" & "</tr><tr>" & _
		"<td>SG Primary</td><td>"&strOut(8) & "</td>" & "</tr><tr>" & _
		"<td>SG Backup</td><td>"&strOut(9) & "</td>" & "</tr><tr>" & _
		"<td>DBA Primary</td><td>"&strOut(10) & "</td>" & "</tr><tr>" & _
		"<td>DBA Backup</td><td>"&strOut(11) & "</td>" & "</tr><tr>" & _
		"<td>Last Updated</td><td>"&strOut(12) & "</tr></table>"
			
	servername.value = UCase(strComputer)
	sixdot.value = strOut(1)
	purpose.value = strOut(2)
	loc.value = strOut(3)
	domain.value = strOut(4)
	sla.value = strOut(5)
	srvcontact.value = strOut(6)
	srvmanager.value = strOut(7)
	sgprimary.value = strOut(8)
	sgbackup.value = strOut(9)
	dbaprimary.value = strOut(10)
	dbabackup.value = strOut(11)

	oConn.Close
End Sub

Sub setproc()
	strComputer = ServerName.Value
	ADLookUp(strComputer)
	if sixdot.value = "" then sixdot.value = "NA"
	if purpose.value = "" then purpose.value = "NA"
	if loc.value = "" then loc.value = "NA"
	if domain.value = "" then domain.value = "NA"
	if sla.value = "" then sla.value = "NA"
	if srvcontact.value = "" then srvcontact.value = "NA"
	if srvmanager.value = "" then srvmanager.value = "NA"
	if sgprimary.value = "" then sgprimary.value = "NA"
	if sgbackup.value = "" then sgbackup.value = "NA"
	if dbaprimary.value = "" then dbaprimary.value = "NA"
	if dbabackup.value = "" then dbabackup.value = "NA"
	oComputer.Put "Description", servername.value&";"&sixdot.value&";"& _
			purpose.value&";"&loc.value&";"&domain.value&";"&SLA.value&";"& _
			srvcontact.value&";"&srvmanager.value&";"&sgprimary.value&";"& _
			sgbackup.value&";"&dbaprimary.value&";"&dbabackup.value&";"&date
	oComputer.SetInfo
	oConn.Close
End Sub

Sub clearproc
	dataarea.InnerHTML = ""
	servername.value = ""
	srvcontact.value = ""
	sixdot.value = ""
	srvmanager.value = ""
	purpose.value = ""
	sgprimary.value = ""
	loc.value = ""
	sgbackup.value = ""
	domain.value = ""
	dbaprimary.value = ""
	sla.value = ""
	dbabackup.value = ""
End Sub

Sub searchproc()

	If Dict.Exists("ADRecords") Then
		Dict.Remove("ADRecords")
	End If
	
	msg = Null
	
	Call ADSearch(search.value)
	For Each item In Dict.ReturnArray("ADRecords")
		temp = Split(item,"|x|")	
		msg = msg &"<b>Name:</b><em> " & UCase(temp(0)) & "</em><br>" _
				& "<b>Description:</b><em> " & temp(1) & "</em><br><br>"
	Next
	
	dataarea.innerhtml = "<input type="&Chr(34)&"button"&Chr(34)&" value="& _
	Chr(34)& "Export to CSV"&Chr(34)&" onclick="&Chr(34)&"export"&Chr(34)& _
	"/><br>" & msg
	
End Sub

Sub export()

	Dim oFso, FileHandle
	Set oFso = CreateObject("Scripting.FileSystemObject")
	temp = Split(Date,"/")
	sDate = temp(0)&temp(1)&temp(2)
		
	Set FileHandle = oFso.OpenTextFile _
		(Dict.CurrentDir&"ServerOut-"&sDate&".csv", 2, True)
		FileHandle.WriteLine "Server,SIXDOT,Purpose,Location,Domain,SLA," & _
"Srv Contact,Srv Manager,SG Primary,SG Backup,DBA Primary,DBA Backup,Modified"
		
		For Each item In Dict.ReturnArray("AdRecords")
			temp = Split(item,"|x|")
			strWrite = Replace(temp(1),",","-")
				strWrite = Replace(strWrite,";",",")
					strWrite = Replace(strWrite,"-",";")
			FileHandle.WriteLine strWrite
		Next
	FileHandle.Close
	
	dataarea.innerhtml = "<p>Report written to:<br>" & _
					"<em>"&Dict.CurrentDir&"serverout-"&sDate&".csv</em></p>"
End Sub

'-----------------Working Functions--------------------------------------------
Public Function ADLookUp(strComputer)

	oConn.Open "Active Directory Provider"
	Set oCmd.ActiveConnection = oConn
	oCmd.CommandText = _
		"Select * from 'LDAP://"&strDomain&"' " _
        & "Where objectCategory='computer' AND name = '"& strComputer &"'" 
	oCmd.Properties("searchscope") = 2
	oCmd.Properties("Page Size") = 1000
	Set oRecord = oCmd.Execute

	For Each item In oRecord.Fields
		Set oComputer = GetObject(item)
	Next
End Function

Public Function ADSearch(strProperty)

	oConn.Open "Active Directory Provider"
	Set oCmd.ActiveConnection = oConn
	oCmd.CommandText = "Select Name, Description, DistinguishedName from " &_
	"'LDAP://"&strDomain&"' Where objectCategory='computer'" 
	oCmd.Properties("Page Size") = 1000
	oCmd.Properties("searchscope") = 2
	Set oRecord = oCmd.Execute
	oRecord.MoveFirst
	
	Do Until oRecord.EOF
	On Error Resume Next 
		Set oComputer = GetObject _
		("LDAP://" & orecord.Fields("distinguishedName").value)
			Description = oComputer.Get("Description")
			If InStr(1,LCase(Description), LCase(strProperty)) <> 0 Then
				Call Dict.ItemList("ADRecords",orecord.Fields("name").value& _
				"|x|" & Description)
			End If
		Description = Null
		oRecord.MoveNext
	Loop
	oConn.Close
End Function
'----------------Class Objects-------------------------------------------------

Class cls_Dict
'Class wrapper for the scripting.dictionary
	Private oDict, oNet, Comparemode, strSplit, oFso, oWShell, oADSI

'---------------------------------------------------------

Private Sub Class_Initialize()
'Dictionary class init subroutine    
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
		Set oDict 	= CreateObject("Scripting.Dictionary")
		Set oNet	= CreateObject("Wscript.Network")
		Set oFso	= CreateObject("Scripting.FileSystemObject")
		Set oWShell = CreateObject("Wscript.Shell")
		Set oADSI = CreateObject("ADSystemInfo")
		
		Dim strUserDomain : strUserDomain = oADSI.DomainDNSName
		Dim strDomain : strDomain = Split(strUserDomain,".")
		For Each item In strDomain
			strDNDomain = strDNDomain & "DC="&item&","
		Next
		oDict.CompareMode = 1
			strSplit = "|:|"
		Call oDict.Add("CurrentDir",oWShell.CurrentDirectory&"")
		Call oDict.Add("computername", oNet.Computername)
		Call oDict.Add("Windir",LCase(oWShell.ExpandEnvironmentStrings _
			("%windir%")))
		Call oDict.Add("CurrentUser",LCase(oNet.UserName))
		Call oDict.Add("Domain",LCase(oNet.UserDomain))
		Call oDict.Add("DomainDN",Left(strDNDomain,(Len(strDNDomain)-1)))
		Call SetOsVer
End Sub

'---------------------------------------------------------

Private Sub Class_Terminate()
'Dictionary class termination subroutine
	If IsObject(oDict) then Set oDict = Nothing
End Sub

'---------------------------------------------------------

Public Property Get CurrentDir
'Returns Current Directory for the script
	CurrentDir = oDict.Item("CurrentDir")
End Property

'---------------------------------------------------------

Public Property Get ComputerName
'Returns the machine name for the current machine
	ComputerName = oDict.Item("computername")
End Property

'---------------------------------------------------------

Public Property Get CurrentUser
'Returns the machine name for the current machine
	CurrentUser = oDict.Item("CurrentUser")
End Property

'---------------------------------------------------------

Public Property Get Domain
'Returns the machine name for the current machine
	Domain = oDict.Item("Domain")
End Property

'---------------------------------------------------------

Public Property Get DistinguishedDomainName
'Returns the Distinguished Name for the Domain
	DistinguishedDomainName = oDict.Item("DomainDN")
End Property

Public Property Get Windir
'Returns the windows directory for the local machine
	Windir = oDict.Item("windir")
End Property

Public Property Get SystemRoot
'Returns the appropriate system directory system32 or syswow64

	If InStr(StrReverse(oDict.Item("CurrentOsVer")), "46x") <> 0 Then 
		SystemRoot = Windir & "syswow64"
	Else
		SystemRoot = Windir & "system32"
	End If

End Property
'---------------------------------------------------------

Public Sub Add(strKey,strValue)
'Method to Add a key and item
	If Debugmode Then On Error Goto 0 Else On Error Resume Next

    Dim EnvVariable, strSplit

    	strSplit = Split(strValue, "%")
	If IsArray(strSplit) Then 
    	EnvVariable = oWShell.ExpandEnvironmentStrings _
    		("%" & strSplit(1) & "%")
    	strValue = strSplit(0) & EnvVariable & strSplit(2)
    			If strValue = "" Then
    				strValue = strSplit(0)
    			End If
    End If
	If oDict.Exists(strKey) Then
		oDict(strKey) = Trim(strValue)
	Else
		oDict.Add strKey, Trim(strValue)
	End If
End Sub

'---------------------------------------------------------

Public Function Exists( strkey)
'Method to check existance of a key
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	
	If oDict.Exists(strKey) then 
		Exists = True
	Else
		Exists = False
	End If

End Function

'---------------------------------------------------------

Public Function Keys()
'Method to retrieve an array of keys
    If Debugmode Then On Error Goto 0 Else On Error Resume Next

	If IsObject(oDict) Then
		Keys = oDict.Keys
	End If
End Function

'---------------------------------------------------------

Public Function Items()
'Method to retrieve an array of items
    If Debugmode Then On Error Goto 0 Else On Error Resume Next

	If IsObject(oDict) Then
		Items = oDict.Items
	End If
End Function

'---------------------------------------------------------
Private Sub SetOsVer()
'Sets a comparable OSVer key item into the dictionary
		If DebugMode Then On Error Goto 0 Else On Error Resume Next

		Dim x, VersionCheck	
		
	VersionCheck = owShell.RegRead("HKLMsoftwaremicrosoft" _
					& "windows ntcurrentversionproductname")

			If ofso.folderexists("c:windowssyswow64") Then
				x = "x64"
			Else
				x = "x86"
			End If
		Call oDict.Add("CurrentOsVer",VersionCheck & " " & x)
End Sub
	Public Property Get OsVer()
		OsVer = oDict.Item("CurrentOsVer")
	End Property

'---------------------------------------------------------
Public Property Get AppName()

	AppName = Left(WScript.ScriptName, Len(WScript.ScriptName) - 4)
End Property 

'---------------------------------------------------------

Public Property Get Key( strKey)
'Property to retrieve item value from specific key
    If Debugmode Then On Error Goto 0 Else On Error Resume Next

		Key = Empty
	If IsObject(oDict) Then
		If oDict.Exists(strKey) Then Key = oDict.Item(strKey)
	End If
End Property

'---------------------------------------------------------

Public Sub ItemJoin(strKey, strItem)
'Method to concactenate new items under one key at the end of the string
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	Dim concat
	If Not oDict.Exists(strKey) Then
		Call oDict.Add(strkey, stritem)
	Else
		concat = oDict.Item(strKey)
		concat = concat & " " & strItem
			oDict.Remove(strKey)
		Call oDict.Add(strKey,concat)
	End If
End Sub

'---------------------------------------------------------

Public Sub ItemList( strKey,  strItem)
'Method to concactenate new items under one key at the end of the string
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	Dim concat
	If Not oDict.Exists(strKey) Then
		Call oDict.Add(strkey, stritem)
	Else
		concat = oDict.Item(strKey)
		concat = concat & "|:|" & strItem
			oDict.Remove(strKey)
		Call oDict.Add(strKey,concat)
	End If
End Sub

'---------------------------------------------------------

Public Sub ItemJoinRev( strKey,  strItem)
'Method to concactenate new items under one key at the start of the string
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	Dim concat
	If Not oDict.Exists(strKey) Then
		Call oDict.Add(strkey, stritem)
		Exit Sub
	Else
		concat = oDict.Item(strKey)
		concat = strItem & " " & concat
			oDict.Remove(strKey)
		Call oDict.Add(strKey,concat)
	End If
End Sub
	
'---------------------------------------------------------	

Public Function ReturnArray( strKey)
'Method to return an item as an array
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	
	Dim ItemToSplit, ItemArray
	
	ItemToSplit = oDict.item(strKey)
	
	ItemArray = Split(ItemToSplit, strSplit)
	
	ReturnArray = ItemArray	
	
End Function
	
'---------------------------------------------------------	

Public Sub Remove( strKey)
'Method to remove a key value
	oDict.Remove(strKey)
End Sub

'---------------------------------------------------------

Public Sub RemoveAll()
'Method to remove all data from the dictionary
	oDict.RemoveAll
End Sub

End Class
'------------------------------------------------------------------------------
</script>

<body bgcolor="silver">
	<table>
	<tr>
	<td>Server</td><td><input type="text" name="servername" size="30"></td>
	<td>Srv Contact</td><td><input type="text" name="srvcontact" size="30"></td>
	</tr>
	<tr>
	<td>SIXDOT</td><td><input type="text" name="sixdot" size="30"></td>
	<td>Srv Manager</td><td><input type="text" name="srvmanager" size="30"></td>
	</tr>
	<tr>
	<td>Purpose</td><td><input type="text" name="purpose" size="30"></td>
	<td>SG Primary</td><td><input type="text" name="sgprimary" size="30"></td>
	</tr>
	<tr>
	<td>Location</td><td><input type="text" name="loc" size="30"></td>
	<td>SG Backup</td><td><input type="text" name="sgbackup" size="30"></td>
	</tr>
	<tr>
	<td>Domain</td><td><input type="text" name="domain" size="30"></td>
	<td>DBA Primary</td><td><input type="text" name="dbaprimary" size="30"></td>
	</tr>
	<tr>
	<td>SLA</td><td><input type="text" name="sla" size="30"></td>
	<td>DBA Backup</td><td><input type="text" name="dbabackup" size="30"></td>
	</tr>
	</table>
	<p>
	<input type="button" value="Get" onclick="getproc"/>
	<input type="button" value="Set" onclick="setproc"/>
	<input type="button" value="Clear" onclick="clearproc"/>
	<input type="button" value="Search" onclick="searchproc"/>
	<input type="text" value="keyword" name="search">
	</p>
	<hr>
	<div id = "DataArea"></div>
</body>

</html>

MMS was a blast. The amount of information, and networking possible is just staggering. I was also pleasantly surprised to find strong brothers in the faith while I was out in Vegas as well.

I’m excited about the future with CM12, Intune, App-v, Powershell, Server 12, Hyper-v, and Ops Man. I had an interesting conversation today over lunch about the essential folly of strict delegated authority and technology specialization that happens inside corporations. I would like to hit a few of the high points I was arguing to substantiate my claim that smaller work forces with broader skill sets offered a stronger IT work force as a whole. In terms of retention, satisfaction, cost of operation, and potential for innovation.

Employee retention and satisfaction will increase.

As I had posted on here previously, money does not serve as a motivational tool for cognitive tasks; which we can all agree on is where most IT engineering level jobs reside. Give enough money that money is not an issue, and you’ve essentially reached the end of money’s power of motivation over your employees. So then how do we motivate these employees to do more? Essentially, give them more to do; or give them diversity in their work and more to solve.

So? What can we conclude from that?

That a highly technical person (who is paid appropriately) will become a more productive (and one could read loyal) employee if given more opportunities to work.

What does delegated authority, and technology specialization teach us? Don’t give them enough money to make money NOT an issue, but instead give them a fixed growth path, and limit their technical involvement to one area. Generally this breeds a mercenary type behavior born of a loss of interest resulting in poor production. This WILL result in higher turn over as employees seek better opportunities, generally using a skill set they cultivated on the previous company’s dime… So why isn’t retention an issue? That’s lost revenue in training, hiring, terminations, and man hours to complete the tasks involved with all of this.

Reduced headcounts, with improved availability

Doing more with less, or less with more. Lets just look at this for a minute and really think this through.

t*(e^x)=c

Technologies that need to be supported (t) times the total number of employees (e) required for each piece (x) then I can get a rough estimate of my required headcount. To keep it simple lets assume that ideally these technologies can be supported 1 for 1. If I find myself in a specialized state the value of x will have to be 2. In some cases you could get away with 1, but if this is a tier 2 or higher item you are at a serious risk in terms of support availability. This is a problem with specialization, you can cross train, but rarely does that cross training work appropriately for hand off in a crisis situation.

However if your standard mode of operation allows for covering multiple areas then workloads will delegate themselves between employees evenly. When one leaves another can step in line to carry that workload without any impact. You have effectively reduced your required workload by 1 per technology. Now of course this is very basic, and doesn’t account for a lot of variables that would exist such as synergies that already exist, but if one adopts distributed responsibility more synergies will be found and that head count will reduce. Salaries will be released to go back into the company, and to the salaries of your remaining talent.

You’ve now found the way to take the subject of money off the table, and increased your support availability. When I talk about availability, I don’t just mean in terms of support, but even in project through put and time lines. You won’t be hit as hard by your “so and so guy”‘s vacation in terms of project completion.

Innovation’s will be found more frequently

I’m not going to go too deep on this as it’s a pretty simple benefit to identify. You have more eyes on something, the greater the chance is you find an improvement to that.

You will have an increasingly more motivated work force

By establishing a distributed responsibility system you have also given your employees two of the 3 fundamental keys for motivation.

  1. Autonomy
  2. Mastery

How so, you might ask? By exposing your employees to more technology and work you have given, or even forced them into a state of self governance. They are given the reigns to see what is needed and respond accordingly to those needs which in turn will result in mastery of the technologies that they support. Look, we can spend as much time as we want discussing how people learn and what makes the best teacher, but inevitably we all know that experience is king.

Great, I’m sold, so what are the pitfalls?

You need to identify the right talent, and you need to always evaluate service level requirements and demand.

These are areas that require strong management to be able to identify. In truth, you could spend a lot finding a solid recruiter for talent selection, but I think your current teams should be more involved in selecting their peers. The reality is, they are your source that should let you know if the new person will have the technical chops and personality to fit in and do the work.

Anyway, I could go on further extolling the merits of this model, but I feel most my points would just be beating a dead horse so I’ll stop now.

Have a good one.


Edit:

So valve’s employee handbook and business model serves as an excellent (and humorous) example for what I’m talking about.

Ran into this on a failing MP repair in our DMZ.  The error was a 1603, and according to the mp.msi logs it was unable to create the virtual directories for the MP to function.  What was looking like a complete IIS rebuild turned out to be a known issue surrounding BITS; and a far simpler solution then I had originally imagined.

The long and short or it; uninstall and reinstall the BITS feature, then reinstall the MP site component and bam.