Mar 192010
 

after a lot of looking and quite a bit of testing and customization, i think i have finally found a replacement for adobe acrobat reader.

why replace acrobat reader? off the top of my head:

  1. security issues. everywhere. frequently.
  2. and hence because of the security issues, you have to patch often. very often. which requires time, testing, and a fair amount of good luck to not break *anything*.
  3. and lastly, i was interested in replacing adobe because of its tendency to crash, specifically in our AD environment (see this post for more details). its not often, but often enough that i get some calls

so here is what i did. i searched. a bunch. i of course found foxit, and i tested foxit pretty heavily. i like foxit, and it was very close to what i was looking for, but then i ran across tracker software’s pdf xchange viewer. not only was it small and fast like foxit, i could mod the heck out of it to get it to look the way i wanted, and it could modify pdf’s, all for free.

heres how i turned this:

into this:

Continue reading »

Mar 042010
 

a while back we migrated our email-to-fax to an external company called myfax. we had an internal faxing service that had given us all sorts of problems, hence the move. one nice thing about the internal faxing service was that you could send an email to 1234567890@domain.fax, and because of a send connector in exchange 2007 it redirected it to the fax server.

so when we moved over to myfax, i wanted to keep the same functionality in place so that users could send things to 1234567890@domain.fax, but then have exchange rewrite the email and change domain.fax to myfax’s domain name. sounds simple enough, right?

unfortunately, if you are running exchange in a small environment and its the only exchange server, you can’t do this. the only way to do this using exchange was to implement a second exchange server, an edge server. now, i like the idea of the edge server. i would like to implement an edge server. but at our size and our budget, it wasn’t a very practical option.

i did get some advice that i could use a product like exclaimer to do rewrites (looked nice, and it could do it). but, back to the numbers, and at the cost of exclaimer, i was almost better off just getting an edge server.

since neither an edge server or exclaimer was an option, i googled a bit and found some things that got me going in the right direction. in the end, i wrote (for the record, wrote = copied and modified someone else’s code with some help) a dll for exchange that does the header rewrite functionality for us.

heres what i had to do:

  1. download and install microsoft visual studio c# 2008 express edition
  2. start a new project in c# express (use the class library template)
  3. add a reference to the following dll’s (both located on your exchange server @ C:\Program Files\Microsoft\Exchange Server\Public
    1. Microsoft.Exchange.Data.Transport
    2. Microsoft.Exchange.Data.Common
    3. point to the dll’s (either point directly to the exchange server using an UNC path, or copy them locally)
  4. now that the dll’s are referenced, copy and paste the following code (replace what was in the original .cs file, don’t append to it).
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Threading;
    using System.Text.RegularExpressions;
    using Microsoft.Exchange.Data.Mime;
    using Microsoft.Exchange.Data.Transport;
    using Microsoft.Exchange.Data.Transport.Email;
    using Microsoft.Exchange.Data.Transport.Routing;
     
    namespace Samples.Agents.MyRoutingAgent
    {
        public class MyRoutingAgentFactory : RoutingAgentFactory
        {
            public override RoutingAgent CreateAgent(SmtpServer server)
            {
                return new MyRoutingAgent();
            }
        }
     
        public class MyRoutingAgent : RoutingAgent
        {
            public MyRoutingAgent()
            {
                base.OnSubmittedMessage += new SubmittedMessageEventHandler(MyRoutingAgent_OnSubmittedMessage);
            }
     
            void MyRoutingAgent_OnSubmittedMessage(SubmittedMessageEventSource source, QueuedMessageEventArgs e)
            {
                Regex regex_fax = new Regex("(\\d+@)domain.fax", RegexOptions.IgnoreCase);
     
                //Alter the P1 headers.The P1 address is used for routing.
                for (int intCounter = e.MailItem.Recipients.Count - 1; intCounter >= 0; intCounter--)
                {
                    if (e.MailItem.Recipients[intCounter].Address.IsValid)
                    {
                        # region email_header_p1_block
                        //Save the whole email address and local part of the address to strings
                        string strEmailAddress = e.MailItem.Recipients[intCounter].Address.LocalPart + "@" + e.MailItem.Recipients[intCounter].Address.DomainPart;
                        string strLocalPart = e.MailItem.Recipients[intCounter].Address.LocalPart;
     
                        //Match using the created regex agains the saved email address string
                        Match match_domain_fax = regex_fax.Match(strEmailAddress);
     
                        //If the match is successful, move on, otherwise exit
                        if (match_domain_fax.Success)
                        {
                            //The match is successful, remove the old address
                            e.MailItem.Recipients.Remove(e.MailItem.Recipients[intCounter]);
     
                            e.MailItem.Recipients.Add(new RoutingAddress(strLocalPart + "@faxing_domain.com"));
                        #endregion
                        }
                    }
                }
     
                //Alter the P2 headers so that the mail displays the correct recipient display Name
                EmailRecipientCollection erToRecipientCollection;
                erToRecipientCollection = e.MailItem.Message.To;
     
                foreach (EmailRecipient rec in erToRecipientCollection)
                {
                    Match match_domain_fax = regex_fax.Match(rec.SmtpAddress);
     
                    if (match_domain_fax.Success)
                    {
                        string xname = regex_fax.Replace(rec.SmtpAddress, "$1faxing_domain.com");
     
                        rec.DisplayName = xname;
                        rec.SmtpAddress = xname;
                    }
                }
            }
        }
    }
  5. build the project. after its built, you should have a dll @ c:\documents and settings\user.name\my documents\visual studio 2008\projects\project_name\project_name\bin\release\project_name.dll (this is on xp sp3)
  6. copy your new dll to a folder on the exchange server (lets use c:\routing_agent)
  7. open up a powershell prompt and run the following commands
    1. net stop msexchangetransport # to stop the exchange transport service
    2. install-transportagent -name “routing_agent_v1″ -assemblypath C:\routing_agent\project_name.dll -transportagentfactory samples.agents.myroutingagent.myroutingagentfactory
    3. enable-transportagent -identity “routing_agent_v1″
    4. get-transportagent -identity “routing_agent_v1″
    5. net start msexchangetransport

thats it, you should now have a working routing agent on a non-edge exchange server that can do header rewrites.

references:
90% of this code was taken from: http://blogs.msdn.com/akashb/archive/2009/02/24/how-to-rewrite-the-to-address-in-transport-agents-on-a-hub-server.aspx
http://gsexdev.blogspot.com/2009/01/from-address-rewriting-in-transport.html
http://msdn.microsoft.com/en-us/library/aa579185.aspx

Mar 032010
 

i have been working on a vbscript that adds a folder into outlook 2007, which on its face doesn’t seem that hard to do.

my co-worker knocked out a script that adds the folder, but i was looking for something that also had some logic to first see if the user had a email enabled account in AD, then, if they did have a mail enabled account, to only run the script once.

what i came up with was a modified script based mostly on http://www.codeproject.com/KB/vbscript/ExchangeMailBox.aspx?msg=946994 that first checked if a user was mail enabled, then i added some logic to look at a reg key that i created to create the outlook folder and registry key, or if the key already exists, to exit the script

heres the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
' outlook_add_folder_v1.vbs
'
' Check if user has Exchange Account by Peter  Verijke - (c) 2003 Computech
' http://www.codeproject.com/KB/vbscript/ExchangeMailBox.aspx?msg=946994
'
' This script checks if a user has an email  account, then checks a registry key to see if a Outlook folder has
' already been created. If it has already been  created, the script exits, otherwise the folder is created and
' the needed registry key is created as well.
'
' Logic in a nutshell:
' Does user have an Exchange account?
'      Yes
'           Does the registry key exist?
'                Yes
'                     Exit
'                No
'                     Create  folder in Outlook
'                     Create  registry key
'                     Exit
'      No
'           Exit

Const HKEY_CLASSES_ROOT = &H80000000
Const HKEY_CURRENT_USER = &H80000001
Const HKEY_LOCAL_MACHINE = &H80000002
Const HKEY_USERS = &H80000003
Const HKEY_CURRENT_CONFIG = &H80000005
 
Dim strKeyPath
Dim strValueName
Dim strValue
Dim strValueCurrent
Dim arrValues
Dim intValue
Dim ArgObj
Dim WshShell ' as object
Dim objEnv ' as collection
Dim objUser 'As IADsUser
Dim objMailbox 'As CDOEXM.IMailboxStore
Dim sUserLDAPName 'As String
Dim DCServer 'As String
Dim olkApp
Dim olkSession
Dim olkParentFolder
Dim olkNewFolder
Dim strComputer
Dim objRegistry
 
On Error Resume Next
 
strComputer = "."
Set objRegistry = GetObject("winmgmts:\\" & strComputer & "\root\default:StdRegProv")
 
' Get the Arguments object
Set ArgObj = WScript.Arguments
 
If ArgObj.Count < 1 Then
     Set WshShell = WScript.CreateObject("WScript.Shell")
     Set objEnv = WshShell.Environment("PROCESS")
     sUserName = objEnv("USERNAME")
Else
     sUserName = UCase(ArgObj(0))
End If
 
' Define your AD Domain Controller
DCServer = "dc01.domain.local"
 
sUserLDAPName = QueryActiveDirectory(sUserName)
Set objUser = GetObject("LDAP://" & DCServer + "/" & sUserLDAPName)
Set objMailbox = objUser
 
'check if user is mailbox enabled
If objMailbox.HomeMDB = "" Then
     'This user does not have a Exchange mailbox
     WScript.Quit 1
Else
     'This user does have an Exchange mailbox
     ' Define your registry keys and values
     strKeyPath = "settings\software\outlook"
     strValueName = "mail_archive_folder_created"
     strValue = "1"
 
     ' Get the current value of the key if it exists and put it into  strValueCurrent
     objRegistry.GetStringValue HKEY_CURRENT_USER,strKeyPath,strValueName,strValueCurrent
 
     ' Check if the value of the key is null, and if it is create the folder  in Outlook and also create the new
     ' registry key to prevent it from being recreated over and over again.
     If (IsNull(strValueCurrent) = True) Then
          Set olkApp = CreateObject("Outlook.Application")
          Set olkSession = olkApp.GetNamespace("MAPI")
          olkSession.Logon "Outlook"
          'Change the parent folder name and path on the following line
          Set olkParentFolder = olkSession.GetDefaultFolder(6).Parent
          'Change the new folder's name on the following line
          Set olkNewFolder = olkParentFolder.Folders.Add("Mail Archive")
          'Change the URL on the following line
          olkNewFolder.WebViewURL = "http://server.domain.local/archive.php"
          olkNewFolder.WebViewOn = True
          olkSession.Logoff
 
          'Create your registry key
          objRegistry.CreateKey HKEY_CURRENT_USER,strKeyPath
          'Set your registry value
          objRegistry.SetStringValue HKEY_CURRENT_USER,strKeyPath,strValueName,strValue
     End If
     WScript.Quit 0
End If
 
Public Function QueryActiveDirectory(sUserName)
'Function:      QueryActiveDirectory
'Purpose:       Search the Active Directory's Global Catalog for users
'Parameters:    UserName - user to search for
'Return:        The user's distinguished name

Dim oAD 'As IADs
Dim oGlobalCatalog 'As IADs
Dim oRecordSet 'As Recordset
Dim oConnection 'As New Connection
Dim strADsPath 'As String
Dim strQuery 'As String
Dim strUPN 'As String

set oRecordSet = CreateObject("ADODB.Recordset")
set oConnection = CreateObject("ADODB.Connection")
 
'Determine the global catalog path
Set oAD = GetObject("GC:")
For Each oGlobalCatalog In oAD
strADsPath = oGlobalCatalog.AdsPath
Next
 
'Initialize the ADO object
oConnection.Provider = "ADsDSOObject"
 
'The ADSI OLE-DB provider
oConnection.Open "ADs Provider"
 
'Create the search string
strQuery = "<" & strADsPath & _
">;(&(objectClass=user)(objectCategory=person)(samaccountName=" &  _
sUserName & "));userPrincipalName,cn,distinguishedName;subtree"
 
'Execute the query
Set oRecordSet = oConnection.Execute(strQuery)
If oRecordSet.EOF And oRecordSet.BOF Then
     'An empty recordset was returned
     QueryActiveDirectory = "Not Found"
Else    'Records were found; loop through them
     While Not oRecordSet.EOF
     QueryActiveDirectory = oRecordSet.Fields("distinguishedName")
     oRecordSet.MoveNext
     Wend
End If
 
End Function

Feb 262010
 

this morning on the way to work, i heard larry (from pauldotcom.com) talking about a script he used to pull the logs from a lot of machines. he mentioned a few things that he didn’t like about the script, and i actually had run into in a similar situation (and had similar dislikes). heres the details (and the solution):

for patching purposes and just good windows hygiene, i wanted to reboot all my workstations nightly.

i googled a bit, and found several ways to do this, but none that did it the way i pictured it.

the most frequent suggestion was to put a list of all the computers you wanted to be rebooted into a text file, then run a script against those computers using a wmi script or psshutdown. in my environment, i quickly found two problems with this:

  1. it was slow. because computers would sometimes be shutdown and the timeout was so long for powered down machines, rebooting hundreds of computers could take a long, long time if enough were shutdown
  2. laziness/forgetfulness. what are the chances of me and everyone i work with remembering to put new machines into the text file to be rebooted? maybe everyone else is better at this than we are, but i knew this had no shot of actually being kept current in our environment

so i set out to find a script that would reboot all computers in an AD OU, bypassing unavailable machines quickly, and not requiring any changes to the script if new machines were added or machines were removed.

here is what i came up with, see comments in the code for an explanation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Dim strFilter, strAttrs, strScope, strDNSSuffix, strBase
Dim objConn, objRS, objShell,objExec,objFSO,objFile, fileName
 
Set objShell = CreateObject("Wscript.Shell")
 
' List out the OU's you want computers in to be rebooted. Remember, if you add an OU,
' increment the strRoot(x) in *both* places.
Dim strRoot(1)
strRoot(0) = "OU=warehouse,DC=domain,DC=local"
strRoot(1) = "OU=terminal_servers,OU=servers,DC=domain,DC=local"
 
' Set the filter for computers only
strFilter = "(objectclass=computer);"
strAttrs  = "name;"
strScope  = "subtree"
 
' Your domain name
strDNSSuffix = ".domain.local"
 
'This is your main loop, each time a different OU.
For i = 0 To UBound(strRoot)
     strBase   =  "<LDAP://" & strRoot(i) & ">;"
     Set objConn = CreateObject("ADODB.Connection")
     objConn.Provider = "ADsDSOObject"
     objConn.Open "Active Directory Provider"
     Set objRS = objConn.Execute(strBase & strFilter & strAttrs & strScope)
     objRS.MoveFirst
 
     'This is your inner loop, each time an individual PC found in the search of the base.
     While Not objRS.EOF
          If objRS.Bookmark Mod 1 = 0 Then
               ' Pause for two seconds (ran into issues if this moved too quick)
               WScript.Sleep 2000
          End If
          ' Call your shutdown computer sub to reboot the individual computers
          ShutDownComputer(objRS.Fields("name").Value & strDNSSuffix)
     Wend
 
     Set objConn = Nothing
     Set objRS = Nothing
 
Next
 
Sub ShutDownComputer(byval strComputer)
     Dim strShutDown,objShell
 
     ' Your psshutdown command with the following options: -r for reboot, -f for force, -c allow the user to cancel the shutdown, -t 300 to give the user
     ' 300 seconds (5 minutes) to close out of applications and save, -e for the error code (planned), and -m for the message the user will see
     strShutDown = "C:\some\path\psshutdown.exe -r -f -c -t 300 -e p:0:0 -m " & chr(34) & "Nightly restart of computer" & chr(34) & " \\" & strComputer
     Set objShell = CreateObject("WScript.Shell")
     objShell.Run strShutdown, 0, False
 
     Set objShell = Nothing
End Sub

psshutdown – http://technet.microsoft.com/en-us/sysinternals/bb897541.aspx
pauldotcom episode 187 notes – http://pauldotcom.com/wiki/index.php/Episode187

Feb 242010
 

i initially was just going to post a fix i had found (via google) to resolve a rediculous problem that adobe acrobat reader 9.x has in it. after reviewing my fix and seeing how ugly the code/logic was, i ended up rewriting the script.

so there are two lessons in this post
1.    adobe will never get their act together (at least with acrobat reader 9)

2.    the fear of peer code review is good, and it forced me to reevaluate some sloppy scripts that “just work”, but do it in a half-baked manor
so here we go, adobe acrobat reader 9.x and its issues with redirected application data on a server 2008 terminal server
so the real problem here is not that adobe writes crappy code. the real problem is that adobe wrote some crappy code and has not fixed it.

here is the problem: if you are running redirected folders in an AD environment and you redirect your application data folder, adobe acrobat reader 9 will give you a c++ runtime error if you are not an administrator. i have seen several people come up with fixes, one works for some, but not for others, so ymmv.

the three fixes i have seen have been:
1.    give list folder / read data permissions on the root level (applied to this folder only) of your users or homes share
2.    create the local low folder
3.    lastly, and the one that i use with a vbscript, is do delete a particular registry key
here was my original code:

1
2
3
4
5
6
7
Option Explicit
 
Dim objShell
Set objShell = WScript.CreateObject(“WScript.Shell”)
 
On Error Resume Next
 
objShell.RegDelete “HKCU\Software\Microsoft\Active Setup\Installed Components\{89820200-ECBD-11cf-8B85-00AA005B4340}\Version”

so, no error checking, and technically speaking it worked, but its ugly. i went back and fixed this to do some logic to see if the key existed before it tried to delete it, and came up with the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
On Error Resume Next
 
Const HKEY_CURRENT_USER = &H80000001
strComputer = “.”
 
Set objRegistry = GetObject(“winmgmts:\\” & strComputer & “\root\default:StdRegProv”)
strKeyPath = “Software\Microsoft\Active Setup\Installed Components\{89820200-ECBD-11cf-8B85-00AA005B4340}”
strValueName = “Version”
objRegistry.GetStringValue HKEY_CURRENT_USER,strKeyPath,strValueName,strValue
 
If (IsNull(strValue) = False) Then
     objRegistry.DeleteValue HKEY_CURRENT_USER, strKeyPath, strValueName
End If

for us, running server 2008 terminal servers, this fixed the problem. users are now successfully running the latest version of adobe acrobat reader 9 with no errors.

if you want to see details of the problem, or look at some of the other solutions (the root share permissions fix or the local low fix), you can look at some of the following threads/links. they go into a lot more detail and explain what is happening and what to try to fix it

http://support.microsoft.com/kb/955555/en-us
http://forums.adobe.com/thread/391738?tstart=0
http://forums.adobe.com/thread/303079

Feb 232010
 

a few months ago i was looking for a network monitoring tool for reporting and alerting reasons.

for example, if a servers ping response time was over 70ms, i wanted an email. or if i wanted to be alerted if a windows service crashed but the box stayed up, something that would sms/text me to let me know. all that and have a nice, pretty heads up display (hud) for management.

i did not find it hard to find software to do this. i did find it hard to get software that would do this for a reasonable price that was easy to set up.

i did try and go the free route at first. one free solution that was free was a tool called “the dude” (http://www.mikrotik.com/thedude.php, bonus points for a good name). if you are running a small shop or can get no budget, it does a lot of things pretty well.

other free options that will do what i was looking for, are open source (which i like and try to support), but require more time and setup that i was interested in investing were:

a good example of why i opted not to use either was the wmi plugins. i tinkered with using wmi to monitor some things, and while it was possible, it was a headache and the thought of doing that on the scale i was looking at was not at all appealing.

so i moved on and decided to look at paid versions and see what they could offer.

after searching around and comparing my options, i ended up testing out and ultimately purchasing a tool called prtg network monitor 7.2.6 by paessler (http://www.paessler.com/prtg)

here are a few of the things i use prtg for.

  • wmi monitoring of cpu, memory, and disk utilization
  • monitors network throughput via snmp (for what i was looking for, just as effective and easier than netflow)
  • ping response times
  • windows service monitoring through wmi (great if a box stays up but a service crashes)
  • vmware esx/esxi/virtualcenter monitoring
  • file/folder/process monitoring via wmi
  • http monitoring (deeper than just “is the page up?”, lets you look for error messages, authenticate, etc.)
  • microsoft sql/oracle/mysql monitoring
  • microsoft exchange monitoring
  • port availability
  • graphing & historical data (again, pictures for management)
  • setup and population of information/devices was *very* good

and, if something you need isn’t there, you can write your own plugins.

prtg did everything i needed and more, and at a fraction of the cost of other tools like ipswitch’s whats up gold or solarwinds orion network monitor

two things i can mention that i don’t like about prtg

  • the windows app – it looks like paessler put together a web app (and one i like) then decided to write a windows app just so they could say they had it. its kludgey, feature-less, and way less practical to use than the web interface. at this point a waste of time to use.
  • the mapping features – one thing i really wanted was to be able to do a heads up display to have something bright and shiny to show management, and something that was easy for us as administrators to see quickly where the problem was. ultimately i was able to accomplish getting together a good hud for this purpose, but not without quite a bit of effort. so much effort in fact, that i felt compelled to email mr. dirk paessler and tell him “hey, love the product, hate the cumbersome mapping”. he did respond, and said that yes, the mapping leaves a lot to be desired, but it would be improved in the next major version. all ceo’s would say that, i image. i hope he backs it up.

obviously prtg has a lot more features that i am not mentioning, but those have been the ones that i can speak for.

in conclusion, after running prtg for 3+ months in production, i am very happy with it. i does everything we need as a company, and all at a reasonable price. while i can live with the sketchy windows app, the mapping really should be straitened out, but even with that drawback i can recommend prtg to others without reservations.

here is some propaganda from the prtg website (although i can say its true in my experience): http://media.paessler.com/common/files/pdf/NWC-test-2009-EN_r805.pdf

Feb 202010
 

i was trying to troubleshoot a remote server 2008 system that where the gui wasn’t responding, you couldn’t log into the machine, but the box was still responding to pings, wmi requests, etc.

my guess is some process is hogging the processor, but without being able to log in, i needed a remote way to do determine if cpu usage is actually the culprit. while there are a few ways that came to mind (wmic, vbscript, etc), i thought of mark russinovich’s pslist tool.

by default, pslist doesn’t show you the cpu usage (it shows cpu time), but with a few options it will spit out what you need. here is what i ended up running:

1
C:\>pslist.exe -s -r 10 -t \\remote_computer

this ran pslist in task manager mode (-s, that shows cpu usage in %), refreshed every 10 seconds (-r 10), and in a process tree (-t), which i find handy for certain situations, all against the remote computer (\\remote_computer).

obviously you will need to run this as a user that has domain rights that gives you enough rights to run this command, locally or remotely.

heres a link to the pslist sysinternals website for download or more detailed information: http://technet.microsoft.com/en-us/sysinternals/bb896682.aspx