Wednesday, May 30, 2012

How to add another person’s mailbox to Outlook 2007

If you regularly need to access another person’s Exchange mailbox you can do so in Outlook quite easily by going to the File menu | Open | Other User’s Folder… However, this becomes quite tiresome because it does not show in the list of Mail Folders in Outlook.

Adding Mailbox to Outlook 2007

If you would like for the mailbox to show up in the Mail Folder list then do the following.

  1. Open Outlook 2007
  2. Open Tools menu | Account Settings…| Data Files
  3. Select your Exchange mailbox.
  4. Click the Settings… button.
  5. Click the Advanced tab.
  6. Click the Add… button
  7. Type in the name of the mailbox in the textbox
  8. Click OK to close out open windows and that should do it.

Owner Permissions

The above should work, but the owner of the mailbox may not have granted you the rights to the mailbox. To grant you permissions the owner of the mailbox just need to:

  1. Open Outlook 2007
  2. Right-click on the mailbox you need access to.
  3. Select the Properties for “<name of mailbox here>”
  4. Click the Permissions tab.
  5. Click the Add… button
  6. Locate your user in the directory add them. Change the permissions as desired.
  7. Click OK to close out open windows and that should do it.

NOTE: The permissions may need to be set on each folder including Deleted Items or any folders the owner has created.

Replying or Sending on Behalf of the owner

In order for you to reply to any messages that are received in this mailbox or compose new emails from this mailbox you need to be added as a delegate for that person. Please note, if you create a new message or click the reply button in Outlook the Message window will act as you would normally see. However, when you actually click send the Exchange server will send back a message saying you don’t have the rights. Specifically, it will say something like

“You are not allowed to send this message because you are trying to send on behalf of another sender without permission to do so. Please verify that you are sending on behalf of the correct sender, or ask your system administrator to help you get the required permissions.”

To fix this, the owner needs to add you as a delegate. To do that, the owner needs to:

  1. Open Outlook 2007
  2. Go to Tools menu | Options… | Delegates tab
  3. Click the Add… button
  4. Find you in the list.
  5. The owner will now be presented with a bunch of options. The default settings are to allow access to the Calendar. They can leave it, or select the appropriate level of access such as None (if you don’t need access to the calendar) for the Calendar. In order to get access to the mailbox (Inbox), they need to change the Inbox drop down list to Editor (can read, create, and modify items).
  6. Click OK buttons.

More Info

For more instructions and background info on this topic, just type additional mailbox into the help box in Outlook 2007. Open the one called Manage another person's mail and calendar items. The help document is quite good and cover everything in depth.

Tuesday, May 15, 2012

A script to archive log files older than n days to a network share

I found that the IIS and SharePoint log files on my servers were getting out of hand and consuming lots of disk space. So, I decided that it would be nice to have these files compressed and moved to a network share for archiving. The files are all text files so I have selected the PPMd compression algorithm that 7zip happens to have available. It is supposed to give 30% BETTER compression than other popular compression algorithms. This can easily be changed if you decide to modify this script to work on files other than text files. I run it from Windows Scheduler on a weekly basis.

The script assumes you are using cscript to call it. Currently it is configured to only look at files with the .log extension, but that can be easily changed. The script is recursive and will look at the directory you pass it and all sub-directories. You can specify how many days of logs to keep. This is based on the last modified date.

' Usage:
' All parameters are required
' To Move all files older than 7 days from "C:\Source\folder\name\here" to "\\destinationHost\Destination\folder\name\here"
' cscript ArchiveFiles.vbs "C:\Source\folder\name\here" "\\destinationHost\Destination\folder\name\here" 7 true

' To Copy all files older than 10 days from "C:\Source\folder\name\here" to "\\destinationHost\Destination\folder\name\here"
' cscript ArchiveFiles.vbs "C:\Source\folder\name\here" "\\destinationHost\Destination\folder\name\here" 10 false

' Original Script copied from: http://gallery.technet.microsoft.com/scriptcenter/de01e926-088f-409b-abf4-e27dbb185597#content
' Brent Vermilion: 2012-05-14: Modified to use 7zip, not use a mapped drive (only unc), is now recursive, and handles files not in sub-directory,
' and added option to keep original file.

'==================
' Parameters
'==================

' maps the parameters to variables for easier understanding
SourceFolderName = WScript.Arguments(0) 'i.e. "C:\inetpub\logs\LogFiles"    Local log file directory
DestShare = WScript.Arguments(1) ' i.e. "\\myhost\d$\Archived Logs\IIS\usbtmp8375msdev"
LogFileAge = CInt(WScript.Arguments(2)) ' i.e. 7 Log files must be older than this number of days to be archived (using "last modified" date)
DeleteOriginalAfterArchive = CBool(WScript.Arguments(3)) ' i.e. true or false

'==================
' Constants
'==================
Const PathFor7zip = "C:\Program Files\7-Zip\7z.exe"
 
'==================
' Variables
'==================

Set objShell = WScript.CreateObject ("WScript.Shell")
 
Set objFSO = CreateObject("Scripting.FileSystemObject")


counter = 0
failedCounter = 0

ProcessFolder(SourceFolderName)

' clean up
Set objFile = Nothing
Set obShell = Nothing
Set objWshNetwork = Nothing

WScript.Echo counter & " files were archived to " & DestShare
WScript.Echo failedCounter & " files FAILED during archiving process."


Function ProcessFolder(folderName)
    Set objWMIService = GetObject("winmgmts:\\")

    '==================================
    ' get files in folder
    '==================================
    strPathName = FormatPath(folderName)
    Set colFiles = objWMIService.ExecQuery("Select * from CIM_DataFile where Path = '" & strPathName & "'")
    
     '==================================
    ' loop through each file and process the ones older than n days
    '==================================
    For Each objFile in colFiles
        If objFile.Extension = "log" Then
            If WMIDateStringToDate(objFile.LastModified) < (Now() - LogFileAge) Then
                ProcessFile objFile, folderName
            End If
        End If
    Next
 
     '=====================================================================================
    ' Connect to local WMI service and get a collection of subfolders for current folder
    '=====================================================================================

    Set colSubfolders = objWMIService.ExecQuery _
        ("Associators of {Win32_Directory.Name='" & folderName & "'} " _
            & "Where AssocClass = Win32_Subdirectory " _
                & "ResultRole = PartComponent")
               
    '=============================================
    ' loop through the sub-folders
    '=============================================
    For Each objFolder in colSubfolders
        ' recursive call
        ProcessFolder(objFolder.Name)
    Next
 

               
End Function
 

Function ProcessFile(objFile, folderName)

    '=================================================
    ' Check if current folder exists on remote system
    ' If not, create it
    '=================================================
    If Not objFSO.FolderExists(DestShare) Then
        CreateDirs(DestShare)
    End If

    '========================================================
    ' Compress file
    ' chr(34) adds ASCII quotes in case path contains spaces
    '========================================================
    matchBasePathIdx = InStr(1,objFile.Name, folderName, 1)
   
    ' get the path and name of the file without the base directory path
    relativeFilename = Mid(objFile.Name, matchBasePathIdx + Len(folderName) + 1)
   
    ' prepend the mapped drive and the base path on the drive we want to write to
    zipFilename = DestShare & "\" & relativeFilename  & ".7z"
   
    ' build the command line
    ' Notice we are using 7zip and the PPMd compression algorithm (-m0=PPMd) that is superior by approx 30% over other modern compression algorithms FOR TEXT.
    ' If you are not compressing text files you may leave off or change this parameter.
    ' More Info on PPMd Compression:
http://www.dotnetperls.com/ppmd
    cmdText = chr(34) & PathFor7zip & chr(34) & " a -t7z " & chr(34) & zipFilename & chr(34) & " " & chr(34) & objFile.Name & chr(34) + " -m0=PPMd"
   
    ' execute the command we built up
    compressReturn = objShell.run (cmdText,0,true)

    '========================================================
    ' Make sure the current file was compressed successfully
    ' If so, delete the file from the source directory
    '========================================================
    If compressReturn = 0 Then
      
        ' Delete the file if it succeeded and we are configured to do so
        if DeleteOriginalAfterArchive = true Then
            WScript.Echo "Deleted: " & objFile.Name
           
            ' Check if file exists to prevent error
            If objFSO.FileExists(objFile.Name) Then
                objFSO.DeleteFile objFile.Name
            End If
           
        End If
       
        counter = counter + 1
        WScript.Echo "SUCCEEDED: " + objFile.Name
    else
        failedCounter = failedCounter + 1
        WScript.Echo "FAILDED: " + objFile.Name & " -> " & zipFilename
    End If

End Function
 
 
Function FormatPath(strFolderName)
'===========================================================================
' Formats strFolderName to add extra backslashes for CIM_DataFile WQL query
' Stolen from TechNet Script Center
'===========================================================================
    arrFolderPath = Split(strFolderName, "\")
    strNewPath = ""
    For i = 1 to Ubound(arrFolderPath)
        strNewPath = strNewPath & "\\" & arrFolderPath(i)
    Next
    FormatPath = strNewPath & "\\"
End Function
 
 
Function WMIDateStringToDate(dtmDate)
'===================================================
' Formats a WMI date string to a usable Date format
' Stolen from TechNet Script Center
'===================================================

    WMIDateStringToDate = CDate(Mid(dtmDate, 5, 2) & "/" & _
    Mid(dtmDate, 7, 2) & "/" & Left(dtmDate, 4) _
    & " " & Mid (dtmDate, 9, 2) & ":" & Mid(dtmDate, 11, 2) & ":" & Mid(dtmDate,13, 2))
End Function
 
 
' Copied from:
http://www.robvanderwoude.com/vbstech_folders_md.php
' Examples:
' UNC path
'CreateDirs "\\MYSERVER\D$\Test01\Test02\Test03\Test04"
' Absolute path
'CreateDirs "D:\Test11\Test12\Test13\Test14"
' Relative path
'CreateDirs "Test21\Test22\Test23\Test24"
Sub CreateDirs( MyDirName )
' This subroutine creates multiple folders like CMD.EXE's internal MD command.
' By default VBScript can only create one level of folders at a time (blows
' up otherwise!).
'
' Argument:
' MyDirName   [string]   folder(s) to be created, single or
'                        multi level, absolute or relative,
'                        "d:\folder\subfolder" format or UNC
'
' Written by Todd Reeves
' Modified by Rob van der Woude
'
http://www.robvanderwoude.com

    Dim arrDirs, i, idxFirst, objFSO, strDir, strDirBuild

    ' Create a file system object
    Set objFSO = CreateObject( "Scripting.FileSystemObject" )

    ' Convert relative to absolute path
    strDir = objFSO.GetAbsolutePathName( MyDirName )

    ' Split a multi level path in its "components"
    arrDirs = Split( strDir, "\" )

    ' Check if the absolute path is UNC or not
    If Left( strDir, 2 ) = "\\" Then
        strDirBuild = "\\" & arrDirs(2) & "\" & arrDirs(3) & "\"
        idxFirst    = 4
    Else
        strDirBuild = arrDirs(0) & "\"
        idxFirst    = 1
    End If

    ' Check each (sub)folder and create it if it doesn't exist
    For i = idxFirst to Ubound( arrDirs )
        strDirBuild = objFSO.BuildPath( strDirBuild, arrDirs(i) )
        If Not objFSO.FolderExists( strDirBuild ) Then
            objFSO.CreateFolder strDirBuild
        End if
    Next

    ' Release the file system object
    Set objFSO= Nothing
End Sub

Thursday, May 10, 2012

Clipboard doesn’t work between remote computer and local computer

I noticed when I got a Windows 7 that when I connect to different servers with Remote Desktop (RDP) that my clipboard was not working. I also noticed that it only happened on particular servers. As it turned out the servers that I was having trouble getting the clipboard to map were servers I had not accessed yet. I typically save the settings for each of the servers I connect to. In the case of these new servers I was just opening the Remote Desktop client and connecting.

So what was going on? There were a couple of things that caused seemingly random behavior.

1. When I got my new Windows 7 laptop I did not open the remote desktop client and gone to the Local Resources tab and checked the Clipboard checkbox and most importantly, I had not clicked the Save button on the General tab. If you don’t click the save button your preference will not be saved. Thus every time you use RDP client you will need to click the Clipboard checkbox, which I was not doing.

2. The other thing that made this seem random was that in most cases I use my saved settings that are specific to each server.

While researching this, I did find an excellent article that talks about how to fix what sounds like the same symptoms, but is actually a server configuration issue (RDP Clipboard mapping not enabled on terminal services) or bug (have to kill and re-launch rdpclip)

Wednesday, May 9, 2012

Changing SharePoint Site Definition to Original Team Site by updating the content database directly

Imagine you have a custom Site Definition in your MOSS 2007 SharePoint installation that is basically the same as the original Team Site Site Definition. In the end, your custom Site Definition ended up not being needed at all because it was so much like the original Team Site Site Definition. Now every time you upgrade SharePoint or stand up another copy of your SharePoint you have to upgrade and install these custom site definitions. This may also unnecessarily complicate any migration to SharePoint Online (part of Office 365). So, you decide you want to change the Site Definition that these sites use, to now use the default Team Site Site Definition instead of your custom one. One of the big requirements is that the we have a full fidelity copy of the site. By that I mean we don’t lose version history of files in document libraries and all site content is preserved. The question is how do you do this?

WARNING: I MUST first tell you these are NOT SUPPORTED or ALLOWED by Microsoft and you should proceed with caution and please test this on a non-production environment first. By directly modifying the SharePoint Content Database you are breaking the rules set in place by MS and it is possible MS would not help you if you have made any of these changes. I of course assume no responsibility for any harm caused by this code.

I have personally used these on production environment after testing that the changes didn’t break anything, but this was my environment and is by no means a representation of your environment.

You can use the Microsoft supported method of doing this using stsadm and export/import as I show you here, but you have to tweak the files before you import, figure out how to keep the alerts and workflows. In the end it is a lot of time and a lot code. To further complicate things, site collections and sites need to be handled differently also. In the end, the risk seemed quite high that I would miss something even after spending days writing code and testing. I found the testing to be very time consuming because of the time required to export and then import even small sites. To slow it down even more, there are only so many sites you can mess up before you just need to restore the content database again so you have more sites to test on.

If your site definition doesn’t add any value to a site really and they are the same as the default site definitions for the most part then you can update the content database directly. Again, this is absolutely not supported by MS and I assume no responsibility for issues this may cause. With that said again, try it on a dev or QA environment and I do lots of testing to convince yourself it works and I if you are lucky this hack will work.

OK here is all you need to do.

Let’s assume your site definition is called MySiteDef.

  1. Open SSMS and connect to your SharePoint content database (often called something like WSS_CONTENT).
  2. Run the following two updates (one for Team Site and one for Document Work space) assuming your site definitions are based on of these. These will affect saving your site as a template and then creating a new site and does not appear to affect the ability of your existing sites to render.
    update alldocs set setuppath = Replace(setuppath, 'MySiteDef', 'STS') where setuppath = 'SiteTemplates\MySiteDef\default.aspx'
    update alldocs set setuppath = Replace(setuppath, 'MySteDef', 'STS') where setuppath = 'SiteTemplates\MySiteDef\defaultdws.aspx'
  3. Next run the following update assuming your id of your site definition is 30001.
    update webs set webtemplate = 1 where webtemplate = 30001

That is it. So simple and best of all there is no downtime. If anyone tries this let me know how it works for you.

Tuesday, May 8, 2012

Changing absolute urls in SharePoint

If you have an instance of SharePoint (2003 or MOSS 2007 and probably 2010 though I have not tried it)that you want to move from prod to a QA or Dev environment there are sometimes absolute urls in the data. It is important to change these to your QA or Dev url otherwise it is easy to think you are testing Dev and next thing you realize you are on Production because you clicked a link that took you there and you did realize it.

WARNING: I MUST first tell you these are NOT SUPPORTED or ALLOWED by Microsoft and you should proceed with caution and please test this on a non-production environment first. By directly modifying the SharePoint Content Database you are breaking the rules set in place by MS and it is possible MS would not help you if you have made any of these changes. I of course assume no responsibility for any harm caused by this code.

I have personally used these on production environment after testing that the changes didn’t break anything, but this was my environment and is by no means a representation of your environment.

Use these queries to get an idea of what urls you may want to change. The is especially important if you have references to other production environments or even other instances of SharePoint.

-- Research Queries
select distinct SiteUrl from SchedSubscriptions
select distinct Url from NavNodes order by 1
select distinct tp_CopySource from AllUserData
select distinct SiteUrl from ImmedSubscriptions
select distinct nvarchar4 from AllUserData where nvarchar4 not like '%@%' and nvarchar4 not like '% %' order by 1

Once you have figured out what you want to replace, modify the scripts below to work for your situation. In general you can just change the two variables, but best if you review before executing.

-- Run these statements to update urls that are absolute
Declare @OldHostUrl as nvarchar(512)
Set @OldHostUrl = '
http://mysharepoint' -- SharePoint Prod

Declare @NewHostUrl as nvarchar(512)
Set @NewHostUrl = 'http://mysharepointdev -- SharePoint Dev

Update Webs set SiteLogoUrl = Replace(SiteLogoUrl, @OldHostUrl, @NewHostUrl) where SiteLogoUrl like @OldHostUrl + '%'
Update SchedSubscriptions set SiteUrl = Replace(SiteUrl, @OldHostUrl, @NewHostUrl) where SiteUrl like @OldHostUrl + '%'
Update NavNodes set Url = Replace(Url, @OldHostUrl, @NewHostUrl) where Url like @OldHostUrl + '%'
Update AllUserData set tp_CopySource = Replace(tp_CopySource, @OldHostUrl, @NewHostUrl) where tp_CopySource like @OldHostUrl + '%'
Update ImmedSubscriptions set SiteUrl = Replace(SiteUrl, @OldHostUrl, @NewHostUrl) where SiteUrl like @OldHostUrl + '%'
Update AllUserData set nvarchar4 = Replace(nvarchar4, @OldHostUrl, @NewHostUrl) where nvarchar4 like @OldHostUrl + '%'

-- use this to update the navnodes (tabs) Above queries
Update Webs set CachedNavDirty = 1