Skip navigation

Tag Archives: scripting

Laziness is the true mother of necessity I think in IT, and the tedious act of viewing multiple properties pages brought about this one liner.  If you too are setting up diverse deployment sets and need to quickly verify multiple deployments for reboot supression state. Here’s a way to do it in powershell:

gwmi -namespace "root\sms\site_<sitecode>" 
-query "select assignmentname from sms_updategroupassignment 
where assignmentname like '%<NAME SEARCH VALUE>%' and suppressreboot = '3'"
 -ComputerName <SITESERVERNAME> |Select -Property assignmentname

<chopped up for readability sake>

Suppressreboot values:
0 = No Suppression
1 = Workstation Suppression
2 = Server Suppression
3 = Server & Workstation Suppression

replace <> values with your relevant search criteria.

More class information.

I’ve updated the inventory enforcement script and post for anyone who utilizes it. It should be cleaner now as it only depends on wmi for the inventory actions.

Here’s the link back.

So in an attempt to quickly extract OS version and Service Pack for a few machines in an environment the idea was presented to pull the data from active directory. The properties exist so the logic seemed sound; and as we’ve discussed this before it’s a pretty easy task with the active directory module in PowerShell, and here’s the code:

$list = gc computers.txt
Import-Module ActiveDirectory
ForEach($item in $list){
                $ado = (get-adcomputer $item -Properties *)
                $pso = New-Object PSObject
$pso | Add-Member -Name "Computer Name" -MemberType NoteProperty `
		-Value $ado.CN
$pso | Add-Member -Name "Operating System" -MemberType NoteProperty `
		-Value $ado.OperatingSystem
$pso | Add-Member -Name "Service Pack" -MemberType NoteProperty `
		-Value $ado.OperatingSystemServicePack

Assuming for the sake of example the name of this script is, get-adservicepack.ps1, and you’ve got your computers.txt file with your computer names in it then we’d run it like this.

./get-adservicepack.ps1 | export-csv -NoTypeInformation MyAdOutput.csv

So what’s happening?

First, we’re taking the get-content command to pull data from a local text file “computers.txt” into a data object and then iterating through it sequentially.  We are then using the computer name as the lookup name with the get-adcomputer cmdlet along with all it’s ad properties and assign it to a variable called ado.

Now we create a PowerShell object and begin to give it some noteproperties with values pulled from our ad object we created from the ad cmdlet then echo it’s contents out by calling it.

When we run the script and pipe it’s output to export-csv –NoTypeInformation we are taking that output and putting it directly into a csv without any of the object information, otherwise it’s a tabled console output.

PowerShell is so boss sometimes…

Maybe we just do all this in one line?

gc computers.txt|ForEach-object{Get-ADComputer $_ -properties *|select -Property name,operatingsystem,operatingsystemservicepack}|export-csv -notypeinformation output.csv

Scroll that line, like a boss.

Gallery entry on Script Center if you want to rate it

So my free time has been spent lately helping with 2 children with walking pneumonia, 1 infant (perfectly healthy) working a full time job with more than a full time workload; and a lot of the cool, fun stuff I’m doing at work (in my opinion anyway)…. I can’t talk about here

.:: sadface ::.

(I still think my wife has been working harder than I have)

Trick or treating was fun, both Will and Maggie had a blast at their last Trunk or Treat @ HSBC, and run around their neighborhood.  Will was all go, all night; and Maggie was super fired up about her candy!  I was exhausted afterwards of course, but I was overjoyed to see their excitement!  Glad I got to TC this week.

As far as my gaming; I’ve been able to play a little bit of Borderlands 2 (not even 20 yet), and Torchlight 2 (wiped out my save so had to start over) which I have enjoyed.  AC3 was released, and Halo 4 is coming next Tuesday.  Alas, my consoles are packed for the move so I won’t be getting/playing them right away which pains me a bit.  I’d get AC3 for PC, but I have already played all the others on my PS3, so I wish to maintain that trend.

And for coding; I’ve been doing a fair amount of (windows) scripting lately, but nothing really ground breaking.  I did write a SQL to email script in both vbscript then re-wrote in PowerShell (since I was informed CDO will be depreciated in future releases of exchange) which I plan on sharing here in another post once I’ve scrubbed them.  They are actually jokingly easy to do, but very useful for anyone that hasn’t got a paid tool for this kind of thing.

In the short term I’m going to share a simple script to quickly gather OS service pack information from AD from a list of machines and dump it to a csv I wrote for a buddy of mine (seriously, in PowerShell this is crazy simple).

Expect to see that post around Monday or Tuesday; until then, have a good one and pray for us (or keep us in your thoughts).  There’s a lot going on, and we all feel a bit overwhelmed.

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:

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.

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

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


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


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")
#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;")
	$cmd = $sqlcon.CreateCommand()
		$cmd.CommandText = "drop table $table"
		$cmd.CommandText = "create table $table (Name varchar(150) not null Primary key,IP varchar(25), Reverse varchar(150), status varchar(50))"
#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.ip)','$($sys.reverse)','$($sys.status)')"; [Void]$cmd.ExecuteNonQuery()}


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!

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.


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:

		<title>Active Directory Asset Manager (ADAM)</title>
	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:

'-----------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 
Sub getproc()
	On Error Resume Next

	strComputer = ServerName.Value
	Description = oComputer.Get("Description")
	If Err.Number <> 0 Then
		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)

End Sub

Sub setproc()
	strComputer = ServerName.Value
	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&";"& _
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
	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>"
	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
	dataarea.innerhtml = "<p>Report written to:<br>" & _
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)
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
	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
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&","
		oDict.CompareMode = 1
			strSplit = "|:|"
		Call oDict.Add("CurrentDir",oWShell.CurrentDirectory&"")
		Call oDict.Add("computername", oNet.Computername)
		Call oDict.Add("Windir",LCase(oWShell.ExpandEnvironmentStrings _
		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"
		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)
		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
		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"
				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)
		concat = oDict.Item(strKey)
		concat = concat & " " & strItem
		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)
		concat = oDict.Item(strKey)
		concat = concat & "|:|" & strItem
		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
		concat = oDict.Item(strKey)
		concat = strItem & " " & concat
		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
End Sub


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

End Class

<body bgcolor="silver">
	<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>
	<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>
	<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>
	<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>
	<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>
	<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>
	<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">
	<div id = "DataArea"></div>


Ok, so I’ve not posted anything for a few days and I felt the need to throw a brief technical post up with a code snippet from a current project I’m working on. I’m building a health check logon script and as part of that framework I wanted to build a logging object. Since the target format has to be vbscript for what I’m doing, I’ve built it as such, and in a format that views nicely inside of trace32 and trace64.

The idea was to build an object that would perform a simple task, write a log file…. then write a log file, or an event, then write only error events, but could also buffer and dump a final error or success log as a split log to another remote location. All configurable via properties, but with only two exposed methods to control it all (write & writeremote). This object is instantiated by:

Dim Logging
Set Logging = New cls_Logging

Call Logging.Write("my message",1)


Simple enough, no? It’s also worth mentioning I wrote the code so that a constant could be set within the instantiating script of DEBUGMODE and if TRUE it will error, else suppresses all error output.

Here’s the code:

'Logging Class ===================================================================

'Not required for WSF, but is when in standard VBS
Const ForAppending = 8

'Log and Event writer object
Class cls_Logging
'Class for logging to file and event viewer

Private oWShell,oNet,oFSo,Filehandle,rFilehandle
Private fPath,strRFPath,fMaxSize,fLogname,strRemoteErr,BoolEvent,BoolRemote,oDict

Private Sub Class_Initialize()
    'Object Init subroutine
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
    Set oWShell 	= CreateObject("Wscript.Shell")
	Set oNet 		= CreateObject("Wscript.Network")
	Set oFSo 		= CreateObject("Scripting.FileSystemObject")
	Set oDict		= CreateObject("Scripting.Dictionary")
		LogEvent 	= False
		RemoteLog	= False
		Path 		= Left(WScript.ScriptFullName,(Len(WScript.ScriptFullName)_
		File 		= LCase(oNet.ComputerName)
		MaxSize = 2
End Sub


Private Sub Class_Terminate()
	'Object Termination subroutine
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	If bOpen Then
	End If
		Set oWShell = Nothing
		Set oNet = Nothing
		Set oFSo = Nothing
		Set Filehandle = Nothing
		Set fPath = Nothing
		Set fMaxSize = Nothing
		Set fLogname = Nothing
		Set BoolEvent = Nothing
End Sub

'File name properties, for changing and retrieving the log file name
Public Property Let File(strFile)
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	If (InStr(StrReverse(strFile),"gol.")) <> 0 Then
		fLogname = strFile
		fLogname = strFile & ".log"
	End If
End Property
		Public Property Get File()
		    If Debugmode Then On Error Goto 0 Else On Error Resume Next
				File = fLogname
		End Property

'Path name properties, for changing and retrieving the path to logs
Public Property Let Path(strPath)
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
		If (InStr(StrReverse(strpath),"\")) <> 1 Then
			fPath = strPath & "\"
			fPath = strPath
		End If
End Property
		Public Property Get Path()
		    If Debugmode Then On Error Goto 0 Else On Error Resume Next
			Path = fPath
		End Property
'Fully concatenated file name property for retrival.
		Public Property Get FullFileName()
			FullFileName = Path & File
		End Property

'Property for setting maximum file size of log file
Public Property Let MaxSize(strVal)
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	fMaxSize = Cint(strVal) * 1048576
End Property
		Public Property Get MaxSize()
    		If Debugmode Then On Error Goto 0 Else On Error Resume Next
			MaxSize = fMaxSize
		End Property

'Boolean property to determine if the filehandle is in use
Private Property Get bOpen()
	If Debugmode Then On Error Goto 0 Else On Error Resume Next	
		If IsObject(Filehandle) Then
			bOpen = True
			bOpen = False
		End If
End Property

Public Property Let LogEvent( blValue)
'Bool property that dictates event viewer rights
	BoolEvent = blValue
End Property
	Private Property Get LogEvent()
		LogEvent = BoolEvent
	End Property
Public Property Let RemoteLog( blValue)
'Bool property that dictates if logging occurs to remote location
	BoolRemote = blValue
End Property
	Private Property Get RemoteLog()
		RemoteLog = BoolRemote
	End Property
Public Property Let RemotePath( strPath)
		If (InStr(StrReverse(strpath),"\")) <> 1 Then
			strRFPath = strPath & "\"
			strRFPath = strPath
		End If
End Property
	Public Property Get RemotePath()
		RemotePath = strRFPath
	End Property

Private Sub RemoteErrBuffer( 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)
		concat = oDict.Item(strKey)
		concat = concat & "|:|" & strItem
		Call oDict.Add(strKey,concat)
	End If

End Sub

Public Function ErrBuffer()
'Method to return contents of the error buffer
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
    Dim ItemToSplit, ItemArray, item
	ItemToSplit = oDict.item("remotelog")
	ItemArray = Split(ItemToSplit, "|:|")
	ErrBuffer = ItemArray
End Function
Public Sub WriteRemote(strVal)
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	If Not CreateRemote Then
		Exit Sub
	End If

		rFilehandle.WriteLine strVal

End Sub

'Subroutine for creating the remote log file and instantiating the handle
Private Function CreateRemote()
    If Debugmode Then On Error Goto 0 Else On Error Resume Next

	Dim FileProperty,Logsize

	CreateRemote = False

	If Not oFso.FolderExists(RemotePath) Then 
		Call Write(RemotePath & " Does not exist, or is unreachable.",3)
			Exit Function
	End If 

	If Not oFSo.FileExists(RemotePath & File) Then
		oFso.CreateTextFile(RemotePath & File)
		oFSo.DeleteFile(RemotePath & File)
			oFso.CreateTextFile(RemotePath & File)
	End If    
		If Not IsObject(rFilehandle) Then
	       	Set rFileHandle = oFSo.OpenTextFile(RemotePath & File, _
	       	 ForAppending, True)
	    End If
	If oFSo.FolderExists(RemotePath) Then
		CreateRemote = True
	End If
End Function

'Subroutine for writing log entries
Public Function Write( msg,  mtype)
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	Dim msgline, etype
		Call Create()

		If Not bOpen Then
			Call Create()
		End If
    msgline = "<![LOG["&msg&"]LOG]!><time="&""""&DatePart("h",Time) _
    &":"&DatePart("n",Time)&":"&DatePart("s",Time)&".000+0"""&" date=""" _
    &Replace(Date,"/","-")&""""&" component="""&Left(WScript.ScriptName, _
    Len(WScript.ScriptName)-Len(".vbs"))&""" context="""" type="""&mtype _
    &""" thread="""" file="""&Left(WScript.ScriptName,Len(WScript.ScriptName)_
    -Len(".vbs"))& """>"

	Filehandle.WriteLine msgline

		Select Case Mtype
			Case 1
				etype = 0
			Case 2
				etype = 2
						If LogEvent Then
					oWShell.LogEvent etype, msg
						End If 
					Call RemoteErrBuffer("remotelog", msg  & "," & "2")
			Case Else
				etype = 1
						If LogEvent Then
					oWShell.LogEvent etype, msg
						End If
					Call RemoteErrBuffer("remotelog", msg  & "," & "1")
		End Select
End Function

'Subroutine for rolling over log file at file size limit
Private Sub Rollover()
    If Debugmode Then On Error Goto 0 Else On Error Resume Next
	If bOpen Then
	End If
	oFso.CopyFile FullFileName, Left(FullFileName,(Len(FullFileName)-1)), True
		oFSo.DeleteFile FullFileName
	Set FileHandle = oFSo.OpenTextFile(FullFileName, ForAppending, True)			
End Sub

'Subroutine for creating the log file and instantiating the handle
Private Sub Create()
    If Debugmode Then On Error Goto 0 Else On Error Resume Next

	Dim FileProperty,Logsize

	If Not oFSo.FileExists(FullFileName) Then
	End If    
		If Not bOpen Then
	       	Set FileHandle = oFSo.OpenTextFile(FullFileName, ForAppending, True)
	    End if
	Set FileProperty = oFSo.GetFile(FullFileName)
				Logsize = FileProperty.size
	If Logsize > MaxSize Then
			Filehandle.WriteLine "\\\\\\\\\\File Size Reached//////////"
		Call Rollover()
	End If
End Sub
End Class

I built a scripting dictionary wrapper as well which was inspired by work from Dan Thomson in his health check script. I’ll most likely post it next after I feel it’s complete.

If you found this object helpful, or otherwise, I would appreciate it if you rated it on script center.

Redid the File Renamer script for you based on what you asked for. This should tag common video files as v and pictures (well everything that isn’t excluded) as p.  I also added a file browser to the script as well as a general graphical interface to kind of make things simpler.  If you don’t want it in there I can easily strip it out and put it back to command line.

Script: Renamer.ps1
Author: Daniel Belcher
Modified: 11/5/11
    $SHELL = New-Object -comObject Shell.Application  
	$FOLDER = $SHELL.BrowseForFolder(0, $MSG, 0, $PATH)
	    if ($FOLDER -ne $NULL) 
		$SHELL = New-Object -ComObject Wscript.Shell
Select Folder with contnets to modify via Shell.Application namespace
and verify that the choice is correct with Wscript popup method.
   $FOLDER = Folder -MSG "Select your folder..." -Path .\
if($FOLDER -eq $NULL) {
Message -MSG "Must select a folder to continue." -Title "Error" 
$VERIFY = (Message `
"You Selected:
Its contents will be renamed, are you sure?" -Title "Verify")
	if($VERIFY -eq "2") {break}
Grab directory contents and process
	$LIST = Get-ChildItem "$FOLDER\*" -Exclude `
	*ps1,*exe,*mp3,*dll,*ini,*cfg,*ocx,*doc?,*xls?,*txt|Sort-Object lastwritetime
			$X = 0
	ForEach($OBJECT in $LIST)
		{$EXTENSION = $OBJECT.ToString().Split("\") | Select -Last 1; 
		$EXTENSION = $EXTENSION.Split(".") | Select-Object -Last 1
	if($EXTENSION.Contains("ps1") -eq $TRUE){break}
$TYPE = "p"
			mov{$TYPE = "v"}
			avi{$TYPE = "v"}
			wmv{$TYPE = "v"}
			mpg{$TYPE = "v"}
			mpeg{$TYPE = "v"}
			$FILEINFO = New-Object System.IO.DirectoryInfo($OBJECT)
				$NAME = $FILEINFO.LastWriteTime.GetDateTimeFormats() |`
					Select-Object -Index 99
				$NAME = "$($NAME) ($($X)) $($TYPE).$($EXTENSION)"
						Write-Output $NAME
			Rename-Item -Path "$OBJECT" $NAME
			$X = $X+1


Feel free to modify this anyway you like, add more extensions, exclude more etc. This was kind of a lazy hack to get you what you told me, and I had a few minutes to kill since I couldn’t sleep tonight.

As a Systems Engineer/SCCM Administrator I spend a lot of time parsing through data, and assisting support technicians in tracking down failing assets.  Now mind you, I have plenty of reports that give me the information I need to identify the machine and users and techs responsible etc, but what happens when I get a random list of employee names from a project manager that has 0 access to user ids or asset numbers for machines?  Well, I have to find that information, then spend time later pointing them to resources I’ve made available for them; but that’s another topic….

Anyway, I face both problems, I’ll receive a list of userids or usernames and have to resolve them one against another.  Well thanks to powershell I’m able to do so quickly and easily through profile functions.  Now, I’ll explain the benefits of profile functions after the code below:


Import-Module activedirectory

Function Get-UserName { 

Get-ADUser $USERID | select name 
Set-Alias gun Get-UserName 
Function Get-Userid { 
$NAME = $NAME + "*" 
    Get-ADUser -Filter {Name -like $NAME} | select samaccountname,name 
Set-Alias guid Get-Userid 

 How do I use profile functions?!?

Powershell, much like the BASH shell in Unix/Linux, has a profile “script” so to speak at startup.  There is a global one found at:

  • %WinDir%System32WindowsPowerShellv1.0Profile.ps1
  • %WinDir%System32WindowsPowerShellv1.0Microsoft.PowerShell_Profile.ps1
  • %WinDir%System32WindowsPowerShellv1.0Microsoft.PowerShellISE_Profile.ps1

The same filename syntax is used for the user profile versions:

  • %UserProfile%My DocumentsWindowsPowerShellProfile.ps1
  • %UserProfile%My DocumentsWindowsPowerShellMicrosoft.PowerShell_Profile.ps1
  • %UserProfile%My DocumentsWindowsPowerShellMicrosoft.PowerShellISE_Profile.ps1

See a pattern?  Simple enough right?  None of these profiles exist by default though, they must be created.  The names are fairly indicative of what they control, but here’s a breakdown:

  • Profile.ps1
    • This governs startup of both the standard powershell, and the ISE.
  • Microsoft.PowerShell_Profile.ps1
    • This governs startup of the standard powershell console only.
  • Microsoft.PowerShellISE_Profile.ps1
    • This governs startup of the ISE powershell console only.

Simple enough right?  Now, for the sake of simplicity, lets build a current user version of the profile.ps1 and save the above code to it.  Make sure you’ve installed the activedirectory cmdlet module provided with windows 7. Now launch powershell and viola you should now have the cmdlets:

  • Get-UserName
  • Get-UserID

and their aliases:

  • GUN
  • GUID

Ok, now what?

Here’s the thing about profile functions.  You can treat them like cmd-lets now.  That also means that you can script against them.  Consider them a static variable for every powershell session that you have configured with this profile.

Pretty cool huh?  One of the most powerful features of the shell is it’s configurability, and profile functions and aliases are the tip of that spear.

In the case of user name capture, or id capture, I’m but a simple gc and for-each statement away from processing the list given to me.

I hope this helps broaden your practical understanding of profiles, and gets your creative juices flowing for building your own administrative tool kits.  Happy scripting.