<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>devtrends.com &#187; Programming and Software Development</title>
	<atom:link href="http://www.devtrends.com/index.php/category/software-development/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.devtrends.com</link>
	<description>developing trends in information technology</description>
	<lastBuildDate>Fri, 03 Sep 2010 21:32:43 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Centralized Desktop and Favorites with a Centralized My Documents?</title>
		<link>http://www.devtrends.com/index.php/centralized-desktop-and-favorites-with-a-centralized-my-documents/</link>
		<comments>http://www.devtrends.com/index.php/centralized-desktop-and-favorites-with-a-centralized-my-documents/#comments</comments>
		<pubDate>Sun, 01 Aug 2010 02:25:37 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[VBScript]]></category>
		<category><![CDATA[Visual Studio]]></category>
		<category><![CDATA[Windows Server 2003]]></category>
		<category><![CDATA[Windows XP]]></category>
		<category><![CDATA[centralized desktop]]></category>
		<category><![CDATA[centralized favorites]]></category>
		<category><![CDATA[migrate desktop]]></category>
		<category><![CDATA[migrate favorites]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=592</guid>
		<description><![CDATA[What about Desktop and Favorites migration? Wouldn’t it be nice if there was a GPO that made the migration of your user’s Desktop and Favorites as easy as the migration of their My Documents? I think it would be nice. Since there isn’t, I created...]]></description>
			<content:encoded><![CDATA[<p>What about Desktop and Favorites migration? Wouldn’t it be nice if there was a GPO that made the migration of your user’s Desktop and Favorites as easy as the migration of their My Documents? I think it would be nice. Since there isn’t, I created a script that would do the migration. This script should be run following a successful My Documents migration as the goal would be to put the files in the same location as the My Documents files.</p>
<p>When I first started the project, I was hoping that my script would run prior to showing the desktop. This is important because if it runs while the user has access to the desktop, that user may start using files that would prevent the script from running correctly (file locks). I had no luck; if anyone has an idea on how to do this, let me know. Otherwise, I created a .NET application that covers the entire screen on the primary monitor (if more than 1 monitor, then the second one is still visible).</p>
<p><strong>BlankScreen</strong></p>
<p>The BlankScreen application is written in VisualStudio .NET 2008 using .NET Framework 2.0 if I remember correctly. There are some changes you may want to make on this application to tailor it for your environment. The first change would be to put your logo on the application form. The second change, and really the more important change, is to change the ProcessPath variable to point to the UNC path of where you plan on putting the desktop.vbs file. This application just runs the VBScript.</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/07/BlankScreen.jpg"><img class="alignnone size-medium wp-image-593" title="BlankScreen" src="http://www.devtrends.com/wp-content/uploads/2010/07/BlankScreen-300x236.jpg" alt="" width="300" height="236" /></a></p>
<p>So you might be thinking, why would you have an application that is capable of much more advanced programming run a VBScript? Well, I wrote the script first and didn’t feel like porting it to VB.NET. If you feel like spending the time, go for it.</p>
<p><strong>desktop.vbs</strong></p>
<p>Enough chatter about the other stuff, on to the real product of this blog post, the VBScript.</p>
<p>The VBScript has four basic functions that it completes one after the other based on the success of the previous function. Step 1 is to get the username of the logged in user. Step 2 is to create the folders in the user’s My Documents location and copy the files into that new folders (nothing is moved). Step 3 is to update the registry for the Desktop and Favorites locations to the newly created folders. Step 4 renames the old folders in the C:\Documents and Settings\[user]\ folder to DesktopOLD and FavoritesOLD. I feared losing user files, so I did not make the script perform anything that couldn’t be reversed.</p>
<p>Just as you had to change a string variable in the BlankScreen application for your specific server, you also have to change the location of your user’s folder on the network. In my case it was \\fileserver\users\.</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/07/desktop_vbs.jpg"><img class="alignnone size-medium wp-image-594" title="desktop_vbs" src="http://www.devtrends.com/wp-content/uploads/2010/07/desktop_vbs-300x257.jpg" alt="" width="300" height="257" /></a></p>
<p>The migration will change the registry keys:</p>
<pre>HKCR\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders, Favorites
HKCR\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders, Desktop
HKCR\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders, Favorites
HKCR\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders, Desktop</pre>
<p>The new locations will look something like this:</p>
<p>Desktop: \\[server]\[share]\[username]\Desktop-[username]<br />
Favorites: \\[server]\[share]\[username]\Favorites-[username]<br />
e.g. \\fileserver\users\aaron\Desktop-aaron\ would be my desktop folder.</p>
<p><strong>Conclusion</strong></p>
<p>Implementing this solution is fairly easy. First modify the VB.NET application and compile it. Place the .exe file in a shared location that your login script for users has permissions. Place the VBScript file in a similar location, as you specified in the VB.NET application, and modify the serverpath variable and email variables. Test with your user account in your login script batch file using something similar to below:</p>
<pre>if %USERNAME% == yourusername (
 rem lets make sure the user has a my documents folder prior to trying to migrate their desktop
 if exist "\\[server]\Users\%USERNAME%" (
  if exist "C:\Documents and Settings\%USERNAME%\Desktop" (
   rem lets make sure they have the 2.0 Framework installed.
   if exist "C:\Windows\Microsoft.Net\Framework\v2.0*" (
    start "Desktop Migration" "\\[server]\[share]\blankscreen.exe"
   )
  )
 )
)</pre>
<p>As a disclaimer, the application AND script are provided without any warranty and is only for educational / informational purposes. If you choose to use it in your environment, production or not, the outcome is your responsibility.</p>
<p><strong>desktop.vbs Code</strong></p>
<pre>On Error Resume Next

'alert user of what is happening...
MsgBox "Please click OK to begin the backup process for your Desktop and Internet Explorer Favorites.  This process could take a few minutes, please be patient and Do NOT Cancel anything, otherwise data could be lost!" &amp; vbCrLf &amp; vbCrLf &amp; "If you have Administrator privileges, your computer will restart after all of your files have been copied!"

'get the current user
user = getUser()
'set the server location, the following \ is required!
serverpath = "\\[server]\Users\"
'if you have a helpdesk solution with email capability:
useemailnotify = 0
smtpfrom = "email@domain.com"
smtpto = "email@domain.com"
smtpserver = "smtp.domain.com"

'create folders
m_cf = createFolderscopyFiles(user)
If m_cf &lt;&gt; 1 Then
	'update the registry and reboot
	m_ur = updateRegistry(user)
	If m_ur &lt;&gt; 1 Then
		'rename the old folders so it does not use them anymore!
		m_rof = renameOldFolders(user)
		'reboot
		Set WSHShell = WScript.CreateObject("WScript.Shell")
		WshShell.Run "C:\WINDOWS\system32\shutdown.exe -r -t 0"
	End If
End If

Function getUser()
	'get the current user environment variable.
	Set oShell = CreateObject( "WScript.Shell" )
	user = oShell.ExpandEnvironmentStrings("%UserName%")

	If Err.Number &lt;&gt; 0 Then
		'we have failure!
		getUser = 1
		Err.Clear
	Else
		getUser = user
	End If
End Function

Function createFolderscopyFiles(user)
	Set objFSO = CreateObject("Scripting.FileSystemObject")

	'lets only do the folder create and file copy if that folder doesn't already exist. Otherwise, its just gonna set the registry and reboot.
	If Not objFSO.FolderExists(serverpath &amp; user &amp; "\Desktop-" &amp; user) Then
		'Create the new Desktop and the Favorites folder.
		objFSO.CreateFolder(serverpath &amp; user &amp; "\Desktop-" &amp; user)
		objFSO.CreateFolder(serverpath &amp; user &amp; "\Favorites-" &amp; user)

		Set oSHApp = CreateObject("Shell.Application")

		'Copy the Desktop files
		Set fromDFolder = oSHApp.Namespace("C:\Documents and Settings\" &amp; user &amp; "\Desktop")
		Set toDFolder = oSHApp.Namespace(serverpath &amp; user &amp; "\Desktop-" &amp; user)
		toDFolder.CopyHere fromDFolder.Items, 16+512

		'Copy the Favorites files
		Set fromFFolder = oSHApp.Namespace("C:\Documents and Settings\" &amp; user &amp; "\Favorites")
		Set toFFolder = oSHApp.Namespace(serverpath &amp; user &amp; "\Favorites-" &amp; user)
		toFFolder.CopyHere fromFFolder.Items, 16+512
	End If

	If Err.Number &lt;&gt; 0 Then
		'we have failure!
		SendEmail user,"failed at createFolderscopyFiles",Err.Description
		createFolderscopyFiles = 1
		Err.Clear
	Else
		createFolderscopyFiles = 0
	End If
End Function

Function updateRegistry(user)
	'lets work on the registry now
	Const HKEY_CURRENT_USER = &amp;H80000001
	Const HKEY_LOCAL_MACHINE = &amp;H80000002

	'update the registry for Desktop and Favorites (User Shell Folders)
	'set Favorites
	Set oFReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" &amp; "." &amp; "\root\default:StdRegProv")
	strKeyPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders"
	strValueName = "Favorites"
	strValue = serverpath &amp; user &amp; "\Favorites-" &amp; user
	oFReg.SetExpandedStringValue HKEY_CURRENT_USER,strKeyPath,strValueName,strValue
	'set Desktop
	Set oDReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" &amp; "." &amp; "\root\default:StdRegProv")
	strKeyPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders"
	strValueName = "Desktop"
	strValue = serverpath &amp; user &amp; "\Desktop-" &amp; user
	oDReg.SetExpandedStringValue HKEY_CURRENT_USER,strKeyPath,strValueName,strValue

	'update the registry for Desktop and Favorites (Shell Folders)
	'set Favorites
	Set otFReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" &amp; "." &amp; "\root\default:StdRegProv")
	strKeyPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
	strValueName = "Favorites"
	strValue = serverpath &amp; user &amp; "\Favorites-" &amp; user
	otFReg.SetStringValue HKEY_CURRENT_USER,strKeyPath,strValueName,strValue
	'set Desktop
	Set otDReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" &amp; "." &amp; "\root\default:StdRegProv")
	strKeyPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
	strValueName = "Desktop"
	strValue = serverpath &amp; user &amp; "\Desktop-" &amp; user
	otDReg.SetStringValue HKEY_CURRENT_USER,strKeyPath,strValueName,strValue

	If Err.Number &lt;&gt; 0 Then
		'we have failure!
		SendEmail user,"failed at updating registry",Err.Description
		updateRegistry = 1
		Err.Clear
	Else
		updateRegistry = 0
	End If
End Function

Function renameOldFolders(user)
	Set objFSO = CreateObject("Scripting.FileSystemObject")
	objFSO.MoveFolder "C:\Documents and Settings\" &amp; user &amp; "\Desktop", "C:\Documents and Settings\" &amp; user &amp; "\DesktopOLD"
	objFSO.MoveFolder "C:\Documents and Settings\" &amp; user &amp; "\Favorites", "C:\Documents and Settings\" &amp; user &amp; "\FavoritesOLD"

	If Err.Number &lt;&gt; 0 Then
		'we have failure!
		SendEmail user,"failed at renaming old folders",Err.Description
		renameOldFolders = 1
		Err.Clear
	Else
		renameOldFolders = 0
	End If
End Function

Sub SendEmail(user,contnt,contntB)
	'if the setting is to send an email, then do it, otherwise...nada.
	If useemailnotify = 1 Then
		'send an email!!
		Set objEmail = CreateObject("CDO.Message")
		objEmail.From = smtpfrom
		objEmail.To = smtpto
		objEmail.Subject = "Desktop and Favorites Migration Issue for " &amp; user
		objEmail.Textbody = "User had a Desktop files migration error." &amp; vbCrLf &amp; vbCrLf &amp; contnt &amp; vbCrLf &amp; vbCrLf &amp; contntB
		objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
		objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = smtpserver
		objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
		objEmail.Configuration.Fields.Update
		objEmail.Send

		If Err.Number &lt;&gt; 0 Then
			'we have failure!
			MsgBox "Your Desktop migration didn't go very well."
			Err.Clear
		Else
			'YAY!
		End If
	End If
End Sub
</pre>
<p><strong>Downloads</strong></p>
<p><a href="http://www.devtrends.com/downloads/BlankScreen.zip" target="_self">BlankScreen.zip</a> &#8211; VB.NET application</p>
<p><a href="http://www.devtrends.com/downloads/desktop.zip" target="_self">desktop.zip</a> &#8211; VBScript</p>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/centralized-desktop-and-favorites-with-a-centralized-my-documents/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Lockdown a Centralized My Documents</title>
		<link>http://www.devtrends.com/index.php/lockdown-a-centralized-my-documents/</link>
		<comments>http://www.devtrends.com/index.php/lockdown-a-centralized-my-documents/#comments</comments>
		<pubDate>Tue, 22 Jun 2010 04:31:34 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Windows Server 2003]]></category>
		<category><![CDATA[My Documents redirection]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=588</guid>
		<description><![CDATA[Recently a coworker and I were tasked with migrating the My Documents folder of all users to a central server. The obvious comes to mind for migrating users data; data backup. However, the true reason we were migrating our users files was to simplify workstation...]]></description>
			<content:encoded><![CDATA[<p>Recently a coworker and I were tasked with migrating the My Documents folder of all users to a central server. The obvious comes to mind for migrating users data; data backup. However, the true reason we were migrating our users files was to simplify workstation management, particularly when we needed to replace a user’s workstation. If the files are stored on the server, there is no need for us to copy data from one workstation to another. You might be thinking, what about the Desktop and Favorites? I’ll have an article on that topic soon.</p>
<p>So, why write this article? Well, we wanted to make sure that user data was not accessible by anyone in the organization except that user and the backup process. This required custom permissions for the user folder, permissions that are not typical.</p>
<p>We used the GPO settings for My Documents redirection, setting the My Documents location to %HOMEPATH%. Obviously this required that the home path must be set for each and every user in Active Directory. The common way to accomplish this is to modify the users and set the home path to the desired server path. This would create the folder automatically for each user and assign full permissions to the user. It also sets Administrators with full permission. Bah.</p>
<p><strong>PowerShell is, well, powerful</strong></p>
<p>The solution that we came up with was to write a PowerShell script that would accomplish the same task as adding a home path in Active Directory. The only difference is that the script sets the folder permissions that we wanted.</p>
<p>The script accomplishes three tasks based on only providing it an Active Directory account, such as domain\user. The first task is to create the user folder on the server defined in the script. The second sets the permissions on that folder, removing Administrators and then adding SYSTEM and the user as Full Control. It also adds the user as the Owner of the folder. Third, it updates Active Directory, modifying the user’s home path to the newly created folder on the server.</p>
<p><strong>The Script</strong></p>
<p>Before I share the script, it comes with a disclaimer. Use at your own risk, the script is merely for informational purposes. You are responsible for your own server and the files and folders on it, and therefore I am not responsible if this script causes any harm.</p>
<pre style="white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;white-space:pre-wrap;word-wrap:break-word;">
##################################################################################
#
#
#  Script name: SetHomeFolder.ps1
#  Author:      Aaron and John
#
#
##################################################################################

param ([string]$User, [switch]$help)

function GetHelp()
{
$HelpText = @"

DESCRIPTION:
NAME: SetHomeFolder.ps1
Creates folder and sets permissions for the specified user.
Creates folder if it does not exist. Removes "Administrators" default permission.

PARAMETERS:
-User            User to create folder for and who should have access (Required)
-help            Prints the HelpFile (Optional)

SYNTAX:
./SetHomeFolder.ps1 -User Domain\UserName

Creates \\[server]\Users\[UserName] and sets new permissions for that folder.

"@
$HelpText

[system.enum]::getnames([System.Security.AccessControl.FileSystemRights])
}

function CreateFolder ([string]$Path)
{
#check if the folder Exists
Write-Host "$Path..." -Foregroundcolor Green

if (Test-Path $Path) {
Write-Host "...Already Exists" -ForeGroundColor Yellow
} else {
Write-Host "...Created"
New-Item -Path $Path -type directory | Out-Null
}
}

function SetAcl ([string]$Path, [string]$User, [string]$Permission)
{
#this trap prevents ugly errors on the screen when attempting to update the ACL for the user.
trap [Exception]
{
write-host "  ERROR: $($_.Exception.Message)" -ForeGroundColor Magenta
continue
}

Write-Host "Setting Permissions..." -ForeGroundColor Green

#get ACL on folder and assign as object
$GetACL = Get-Acl $Path

#set up the access rules
$Allinherit = [system.security.accesscontrol.InheritanceFlags]"ContainerInherit, ObjectInherit"
$Allpropagation = [system.security.accesscontrol.PropagationFlags]"None"
$AccessRule = New-Object system.security.AccessControl.FileSystemAccessRule($User, $Permission, $AllInherit, $Allpropagation, "Allow")
$SystemAccessRule = New-Object system.security.AccessControl.FileSystemAccessRule("SYSTEM", "FullControl", $AllInherit, $Allpropagation, "Allow")
$AdminAccessRule = New-Object system.security.AccessControl.FileSystemAccessRule("Administrators", "ReadAndExecute", $AllInherit, $Allpropagation, "Allow")

#check if Access for Administrators already exists - and REMOVE
Write-Host "...Removing Permission For: Administrators" -ForeGroundColor DarkGray
$GetACL.RemoveAccessRuleAll($AdminAccessRule) | Out-Null
#end of Administrators permissions

#check if Access for User Already Exists - and ADD
if ($GetACL.Access | Where { $_.IdentityReference -eq $User}) {
Write-Host "...Modifying Permissions For: $User" -ForeGroundColor Yellow

$AccessModification = New-Object system.security.AccessControl.AccessControlModification
$AccessModification.value__ = 2
$Modification = $False
#modify the ACL rule
$GetACL.ModifyAccessRule($AccessModification, $AccessRule, [ref]$Modification) | Out-Null
} else {
Write-Host "...Adding Permission: $Permission For: $User"
# add the ACL rule
$GetACL.AddAccessRule($AccessRule)
}
#end of User permissions

#check if Access for SYSTEM already exists - and ADD
if ($GetACL.Access | Where { $_.IdentityReference -eq "SYSTEM"}) {
Write-Host "...Modifying Permissions For: SYSTEM" -ForeGroundColor Yellow

$SystemAccessModification = New-Object system.security.AccessControl.AccessControlModification
$SystemAccessModification.value__ = 2
$SystemModification = $False
# modify the ACL rule
$GetACL.ModifyAccessRule($SystemAccessModification, $SystemAccessRule, [ref]$SystemModification) | Out-Null
} else {
Write-Host "...Adding Permission: FullControl For: SYSTEM"
#add the ACL rule
$GetACL.AddAccessRule($SystemAccessRule)
}
#end of SYSTEM permissions

#set the owner of the folder to the specified user
$GetACL.SetOwner((new-object System.Security.Principal.NTAccount("$User")))

#this command performs the actual update using the objects as defined above
Set-Acl -aclobject $GetACL -Path $Path
}

function Get-ADUser( [string]$samid=$env:username)
{
#find the user object in active directory and returns the user object to the calling line
$searcher=New-Object DirectoryServices.DirectorySearcher
$searcher.Filter="(&amp;(objectcategory=person)(objectclass=user)(sAMAccountname=$samid))"
$aduser=$searcher.FindOne()

if ($aduser -ne $null )
{
$aduser.getdirectoryentry()
}
}

if ($help)
{
#the user requested help, so let us display that help.
GetHelp
}

#set some global variables per our environment.
$Permission = "FullControl"
$username = $User.substring($User.indexof("\") + 1)
$Path = "\\[server]\Users\$username"

#LET'S BEGIN (sub Main()) - Process folder creation and security changes
if ($Path -AND $User -AND $Permission)
{
#1. create the folder
CreateFolder $Path

#2. set the ACL permissions and ownership for that folder
SetAcl $Path $User $Permission

#3. update active directory with the new path location
Write-Host "Updating Active Directory Home Folder..."
$aduser = Get-ADUser $username
$aduser.homeDirectory = "$Path"
$aduser.homeDrive = "P:"
$aduser.SetInfo()
Write-Host "...$username Updated with: $Path" -ForeGroundColor Green
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/lockdown-a-centralized-my-documents/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Limiting the output of a SharePoint field in a dataview to [x] words.</title>
		<link>http://www.devtrends.com/index.php/limiting-the-output-of-a-sharepoint-field-in-a-dataview-to-x-words/</link>
		<comments>http://www.devtrends.com/index.php/limiting-the-output-of-a-sharepoint-field-in-a-dataview-to-x-words/#comments</comments>
		<pubDate>Wed, 31 Mar 2010 16:57:17 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[SharePoint]]></category>
		<category><![CDATA[XSL]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=578</guid>
		<description><![CDATA[This solution was created by a guy named Marc, the XSL templates he created are just, well, freaking fantastic! View his blog entry here: Displaying the First N Words of a Long Rich Text Column with XSL I have been looking for a solution to...]]></description>
			<content:encoded><![CDATA[<p>This solution was created by a guy named Marc, the XSL templates he created are just, well, freaking fantastic! View his blog entry here: <a href="http://mdasblog.wordpress.com/2009/01/20/displaying-the-first-n-words-of-a-long-text-column-with-xsl/" target="_new">Displaying the First N Words of a Long Rich Text Column with XSL</a></p>
<p>I have been looking for a solution to cleanly limit the output of a SharePoint list field. Most of the time I find solutions that I piece together to make my desired outcome. However, with FirstNWords and the StripHTML templates that Marc shared, there was nothing left to be desired.</p>
<p>For my implementation of his solution: at the beginning of my row template I added the xsl:variable tag, effectively stripping the HTML from the @SRDetails field and placing into $BodyText. Then where I wanted to display the output, I added the xsl:call-template tag for FirstNWords with the appropriate xsl:with-param values. The actual template code was placed directly below the row template, with the only changes being:</p>
<p>1. StripHTML: replace the &lt; sign in the xsl:when to &amp;lt; .<br />
2. StripHTML: replace the other two signs (&lt; and &gt;) as Marc stated.<br />
3. FirstNWords: replace the &gt; signs in the first and second xsl:when to &amp;gt; .</p>
<p>Awesome solution&#8230;thanks Marc.</p>
<p>-Aaron Gilbert</p>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/limiting-the-output-of-a-sharepoint-field-in-a-dataview-to-x-words/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Deploying Applications with the Possibility of a Slow Connection (Robocopy)</title>
		<link>http://www.devtrends.com/index.php/deploying-applications-with-the-possibility-of-a-slow-connection/</link>
		<comments>http://www.devtrends.com/index.php/deploying-applications-with-the-possibility-of-a-slow-connection/#comments</comments>
		<pubDate>Fri, 05 Feb 2010 19:43:49 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[Batch Files]]></category>
		<category><![CDATA[Featured 2]]></category>
		<category><![CDATA[Microsoft O/S]]></category>
		<category><![CDATA[robocopy]]></category>
		<category><![CDATA[Windows]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=523</guid>
		<description><![CDATA[Regardless of which software deployment suite you use in your organization, there is no way to avoid transferring the majority of the installation files to the local machine, whether that is a file copy or a load into memory / temporary location. For small application...]]></description>
			<content:encoded><![CDATA[<p>Regardless of which software deployment suite you use in your organization, there is no way to avoid transferring the majority of the installation files to the local machine, whether that is a file copy or a load into memory / temporary location. For small application deployments, running them directly from a share location on a server isn’t necessarily a bad idea, even if the user is connected remotely. However, on the other side of the spectrum, installing Microsoft Office from a server share, while connected remotely, is a bad idea.</p>
<p>So what is the solution? How about staging the files on the local hard drive prior to starting the installation? Your first thought might be a batch file or command script that uses xcopy – at least that was my first thought. However, xcopy assumes that your connection will not drop and that it is fairly fast. What happens when the user is remote and while copying the files the user disconnects from the Internet and Virtual Private Network (VPN)? The file copy process has to start over…</p>
<p>With robocopy, a Microsoft product, you can configure it to copy with resume capabilities. In addition, if you show the installation process to your users (different discussion), each file in the copy process displays its current transferred status (0-100%).</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/02/robocopy1.jpg"><img class="alignnone size-medium wp-image-524" title="robocopy1" src="http://www.devtrends.com/wp-content/uploads/2010/02/robocopy1-300x151.jpg" alt="" width="300" height="151" /></a></p>
<p><strong>Getting RoboCopy</strong></p>
<p>Robocopy is available for free from Microsoft as part of the Microsoft Windows Server 2003 <a href="http://go.microsoft.com/fwlink/?LinkId=72969" target="_blank">Resource Kit Tools</a>. The command line options are somewhat daunting, something you’d expect to see from the “grep &#8211;help” command in Linux. In this article we only use a small amount of the power that is Robocopy. For those that dislike command line tools, but still need the functionality of Robocopy, have a look at the GUI tools available for Robocopy.</p>
<ul>
<li><a href="http://technet.microsoft.com/en-us/magazine/2006.11.utilityspotlight.aspx" target="_blank">Utility Spotlight Robocopy GUI</a></li>
<li><a href="http://technet.microsoft.com/en-us/magazine/2009.04.utilityspotlight.aspx" target="_blank">Utility Spotlight RichCopy</a></li>
</ul>
<p><strong>Simple Deployment Script (Adobe Reader)</strong></p>
<p>Below is an example for deploying Adobe Reader to clients utilizing the Robocopy tool.</p>
<pre style="padding-left: 30px;">@ECHO OFF</pre>
<pre style="padding-left: 30px;">rem Create log folder, just in case it does not exist
md C:\installer
md C:\installer\logs
md C:\installer\AdobeReader9.3.0</pre>
<pre style="padding-left: 30px;">rem Bring down the files to the local drive
"\\installer\dist$\robocopy.exe" "\\installer\dist$\AdobeReader" "c:\installer\AdobeReader9.3.0" *.* /E /Z</pre>
<pre style="padding-left: 30px;">rem begin installation
msiexec.exe /i c:\installer\AdobeReader9.3.0\AcroRead.msi TRANSFORMS=c:\installer\AdobeReader9.3.0\AcroRead.mst /qn /l*v C:\installer\logs\AdobeReader9.3.0.log</pre>
<p><strong>Robocopy Command Line Options</strong></p>
<pre>C:\&gt;robocopy /?

-------------------------------------------------------------------------------
   ROBOCOPY     ::     Robust File Copy for Windows     ::     Version XP010
-------------------------------------------------------------------------------

  Started : Fri Feb 05 10:30:35 2010

              Usage :: ROBOCOPY source destination [file [file]...] [options]

             source :: Source Directory (drive:\path or \\server\share\path).
        destination :: Destination Dir  (drive:\path or \\server\share\path).
               file :: File(s) to copy  (names/wildcards: default is "*.*").

::
:: Copy options :
::
                 /S :: copy Subdirectories, but not empty ones.
                 /E :: copy subdirectories, including Empty ones.
             /LEV:n :: only copy the top n LEVels of the source directory tree.

                 /Z :: copy files in restartable mode.
                 /B :: copy files in Backup mode.
                /ZB :: use restartable mode; if access denied use Backup mode.

  /COPY:copyflag[s] :: what to COPY (default is /COPY:DAT).
                       (copyflags : D=Data, A=Attributes, T=Timestamps).
                       (S=Security=NTFS ACLs, O=Owner info, U=aUditing info).

               /SEC :: copy files with SECurity (equivalent to /COPY:DATS).
           /COPYALL :: COPY ALL file info (equivalent to /COPY:DATSOU).
            /NOCOPY :: COPY NO file info (useful with /PURGE).

             /PURGE :: delete dest files/dirs that no longer exist in source.
               /MIR :: MIRror a directory tree (equivalent to /E plus /PURGE).

               /MOV :: MOVe files (delete from source after copying).
              /MOVE :: MOVE files AND dirs (delete from source after copying).

       /A+:[RASHNT] :: add the given Attributes to copied files.
       /A-:[RASHNT] :: remove the given Attributes from copied files.

            /CREATE :: CREATE directory tree and zero-length files only.
               /FAT :: create destination files using 8.3 FAT file names only.
               /FFT :: assume FAT File Times (2-second granularity).
               /256 :: turn off very long path (&gt; 256 characters) support.

             /MON:n :: MONitor source; run again when more than n changes seen.
             /MOT:m :: MOnitor source; run again in m minutes Time, if changed.

      /RH:hhmm-hhmm :: Run Hours - times when new copies may be started.
                /PF :: check run hours on a Per File (not per pass) basis.

             /IPG:n :: Inter-Packet Gap (ms), to free bandwidth on slow lines.

::
:: File Selection Options :
::
                 /A :: copy only files with the Archive attribute set.
                 /M :: copy only files with the Archive attribute and reset it.
    /IA:[RASHCNETO] :: Include only files with any of the given Attributes set.
    /XA:[RASHCNETO] :: eXclude files with any of the given Attributes set.

 /XF file [file]... :: eXclude Files matching given names/paths/wildcards.
 /XD dirs [dirs]... :: eXclude Directories matching given names/paths.

                /XC :: eXclude Changed files.
                /XN :: eXclude Newer files.
                /XO :: eXclude Older files.
                /XX :: eXclude eXtra files and directories.
                /XL :: eXclude Lonely files and directories.
                /IS :: Include Same files.
                /IT :: Include Tweaked files.

             /MAX:n :: MAXimum file size - exclude files bigger than n bytes.
             /MIN:n :: MINimum file size - exclude files smaller than n bytes.

          /MAXAGE:n :: MAXimum file AGE - exclude files older than n days/date.
          /MINAGE:n :: MINimum file AGE - exclude files newer than n days/date.
          /MAXLAD:n :: MAXimum Last Access Date - exclude files unused since n.
          /MINLAD:n :: MINimum Last Access Date - exclude files used since n.
                       (If n &lt; 1900 then n = n days, else n = YYYYMMDD date).

                /XJ :: eXclude Junction points. (normally included by default).

::
:: Retry Options :
::
               /R:n :: number of Retries on failed copies: default 1 million.
               /W:n :: Wait time between retries: default is 30 seconds.

               /REG :: Save /R:n and /W:n in the Registry as default settings.

               /TBD :: wait for sharenames To Be Defined (retry error 67).

::
:: Logging Options :
::
                 /L :: List only - don't copy, timestamp or delete any files.
                 /X :: report all eXtra files, not just those selected.
                 /V :: produce Verbose output, showing skipped files.
                /TS :: include source file Time Stamps in the output.
                /FP :: include Full Pathname of files in the output.

                /NS :: No Size - don't log file sizes.
                /NC :: No Class - don't log file classes.
               /NFL :: No File List - don't log file names.
               /NDL :: No Directory List - don't log directory names.

                /NP :: No Progress - don't display % copied.
               /ETA :: show Estimated Time of Arrival of copied files.

          /LOG:file :: output status to LOG file (overwrite existing log).
         /LOG+:file :: output status to LOG file (append to existing log).

               /TEE :: output to console window, as well as the log file.

               /NJH :: No Job Header.
               /NJS :: No Job Summary.

::
:: Job Options :
::
       /JOB:jobname :: take parameters from the named JOB file.
      /SAVE:jobname :: SAVE parameters to the named job file
              /QUIT :: QUIT after processing command line (to view parameters).

              /NOSD :: NO Source Directory is specified.
              /NODD :: NO Destination Directory is specified.
                /IF :: Include the following Files.</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/deploying-applications-with-the-possibility-of-a-slow-connection/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Move Files with Folder Actions and AppleScript</title>
		<link>http://www.devtrends.com/index.php/move-files-with-folder-actions-and-applescript/</link>
		<comments>http://www.devtrends.com/index.php/move-files-with-folder-actions-and-applescript/#comments</comments>
		<pubDate>Sat, 23 Jan 2010 05:09:17 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[AppleScript]]></category>
		<category><![CDATA[Featured 2]]></category>
		<category><![CDATA[Folder Actions]]></category>
		<category><![CDATA[POSIX]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=512</guid>
		<description><![CDATA[In relation to my article on Combining PDFs in Apple Automator, I needed to automate moving files from one location to another, with the final destination name of the file being different than the original. At first I thought this would be 5 minutes in...]]></description>
			<content:encoded><![CDATA[<p>In relation to my article on <a href="http://www.devtrends.com/index.php/combine-pdfs-in-apple-automator/">Combining PDFs in Apple Automator</a>, I needed to automate moving files from one location to another, with the final destination name of the file being different than the original. At first I thought this would be 5 minutes in AppleScript, until I realized, as with all programming, that I would run into programming language complexities. The first one was moving to a mounted location, the second was formatting dates and the third was getting a single object in the array, added_items, to output the UNIX style path.</p>
<p>The script to accomplish this task ended up looking like this:</p>
<pre style="padding-left: 30px;"><strong>on</strong> adding folder items to this_folder after receiving added_items<strong>
  repeat</strong> <strong>with</strong> this_item <strong>in</strong> added_items<strong>
    set</strong> destpath <strong>to</strong> "/Volumes/[mount name]/[share]/" <strong>as</strong> string<strong>
    set</strong> datestring <strong>to</strong> ((year <strong>of</strong> (current date)) * 10000) + ((month <strong>of</strong> (current date) <strong>as</strong> integer) * 100) + (day <strong>of</strong> (current date)) <strong>as</strong> integer<strong>
    set</strong> destfilename <strong>to</strong> "thefile_" &amp; datestring &amp; ".pdf" <strong>as</strong> string<strong>
    set</strong> sourcepath <strong>to</strong> POSIX path <strong>of</strong> this_item<strong>
  end</strong> <strong>repeat
end</strong> adding folder items to</pre>
<p>If you are familiar with Visual Basic, the &#8220;repeat with this_item in added_items&#8221; would be similar to &#8220;for each this_item in added_items&#8221;. This creates a single object (this_item) from the array of objects (added_items). The destpath is a variable of the location that we will write the final file, which in this case is a mounted server.</p>
<p><strong>datestring</strong></p>
<p>The date format was not as easy to accomplish as I would have expected, especially considering that AppleScript is supposed to simple to use with its English language type structure. To create a date of 20100101, I had to use traditional math starting with the current year multiplied by 10000, which would end up:</p>
<pre style="padding-left: 30px;">2010 * 10000 = 20100000</pre>
<p>Next I needed to add the month in the appropriate location in the integer. This was accomplished by current month multiplied by 100, which would end up:</p>
<pre style="padding-left: 30px;">01 * 100 = 100</pre>
<p>Add the month to the year integer and you have:</p>
<pre style="padding-left: 30px;">20100000 + 100 = 20100100</pre>
<p>Finally, we need to add the day, which as you can imagine, needs no modification:</p>
<pre style="padding-left: 30px;">20100100 + 01 = 20100101</pre>
<p>The final number is stored in the variable datestring.</p>
<p><strong>sourcepath</strong></p>
<p>After about 30 minutes of frustration, working with other avenues to accomplish the same task, I finally found an AppleScript example that used &#8220;POSIX path of&#8221;. Being that I am a native Windows guy, POSIX is fairly foreign to me, however, it is key to making sure the shell command, &#8220;mv&#8221;, works properly.</p>
<p>If you were to use:</p>
<pre style="padding-left: 30px;">set sourcepath to name of this_item</pre>
<p>you  would get a path separated with colons, such as harddrive:Volumes:[mount name]:[share], which is clearly useless in a shell environment. So instead, we must request the POSIX path, which is the full path and file name of &#8220;this_item&#8221;:</p>
<pre style="padding-left: 30px;">set sourcepath to POSIX path of this_item</pre>
<p>That is it&#8230;assign this script to a folder using the Folder Actions functionality of the Mac operating system.</p>
<p>-Aaron Gilbert</p>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/move-files-with-folder-actions-and-applescript/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Alert (Display Dialog) in AppleScript</title>
		<link>http://www.devtrends.com/index.php/alert-display-dialog-in-applescript/</link>
		<comments>http://www.devtrends.com/index.php/alert-display-dialog-in-applescript/#comments</comments>
		<pubDate>Sat, 23 Jan 2010 04:40:12 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[AppleScript]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[display dialog]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=510</guid>
		<description><![CDATA[To display an alert or dialog prompt in AppleScript is easy, once you know the command string to use. For simple scripting, such as AppleScript or VBScript, I will frequently use alert dialogs to display the contents of variables. Hmm, any other reasons for a...]]></description>
			<content:encoded><![CDATA[<p>To display an alert or dialog prompt in AppleScript is easy, once you know the command string to use. For simple scripting, such as AppleScript or VBScript, I will frequently use alert dialogs to display the contents of variables. Hmm, any other reasons for a dialog prompt? Obviously, the true intent is to provide some type of user interface/input&#8230;</p>
<pre style="padding-left: 30px;">display dialog the [string variable]
  buttons {"Yes", "No"}
  default button 1
  with icon 1
  giving up after [(x) seconds]</pre>
<p>To customize this, replace [string variable] with a string in quotes or a string variable name. The buttons array will let you define the text of each button, if you wanted only an &#8220;Ok&#8221; use {&#8220;Ok&#8221;}. The default button 1 defines which button is automatically selected. Giving up after (x) seconds will close the dialog automatically if the user does not respond.</p>
<pre style="padding-left: 30px;">set my_variable to text returned of (display dialog the [string variable]
  buttons {"Yes", "No"}
  default button 1
  with icon 1)</pre>
<p>The above example allows you to use a &#8220;display dialog&#8221; as a user prompt, requiring some type of input that the remaining AppleScript can then process through logic, if, select, et cetera.</p>
<p>As you might have imagined, there are more options available for &#8220;display dialog&#8221;, including user input (text and multiple buttons). If you want more information on the &#8220;display dialog&#8221; method, check out this article on <a href="http://en.wikibooks.org/wiki/AppleScript_Programming/Advanced_Code_List/Display_Dialog" target="_blank">wikibooks</a>.</p>
<p>-Aaron Gilbert</p>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/alert-display-dialog-in-applescript/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Startup Notification</title>
		<link>http://www.devtrends.com/index.php/startup-notification/</link>
		<comments>http://www.devtrends.com/index.php/startup-notification/#comments</comments>
		<pubDate>Thu, 21 Jan 2010 21:16:01 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[Workstation Management]]></category>
		<category><![CDATA[GPO]]></category>
		<category><![CDATA[Startup]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=500</guid>
		<description><![CDATA[How do you know when an approved and domain-joined workstation is turned on prior to someone even signing into the workstation? Or maybe, you are asking, why would I even want to know this? For those that work with network vulnerabilities scanning, such as the...]]></description>
			<content:encoded><![CDATA[<p>How do you know when an approved and domain-joined workstation is turned on prior to someone even signing into the workstation? Or maybe, you are asking, why would I even want to know this? For those that work with network vulnerabilities scanning, such as the product by McAfee’s Foundstone division, this may be of importance. Imagine this scenario. Holiday season arises, everyone loves to take time off and you have an employee that took 2-3 weeks off. When that employee returns, there is a good chance that their computer will be out-dated. From a security standpoint, this is minimal, as it would likely begin receiving updates immediately following a successful sign-on. However, from a political standpoint, if Foundstone is used as a means of judging overall “security”, this workstation could significantly lower that score.</p>
<p>I already know what you are thinking…however, that does not eliminate the fact that dashboard reports are favored by CIOs, IT Directors and the like. Better have that score up to 100%!</p>
<p><strong>StartupNotification</strong></p>
<p>I wrote an application, StartupNotification, that helps with maintaining good scores. Keep in mind that this application only provides a means for notifying the appropriate personnel when a workstation is brought on the network that could potentially “damage”. Second, for this application to be effective in notifying, it must be added as a domain GPO or local policy startup script.</p>
<p><span style="text-decoration: underline;">How It Works</span></p>
<p>Ran from a Windows computer startup script, StartupNotification checks a SQL database for previous records associated with the name of the workstation running StartupNotification. If no records exist, it reports an “Untracked” workstation; if record(s) exist and the latest record is (x) days old, it reports a “Tracked” workstation. After checking the status of the workstation, StartupNotification will create a new record with the workstation name and date of transaction, which will be used at the next StartupNotification check in.</p>
<p><span style="text-decoration: underline;">The Application</span></p>
<p>Download <a href="http://www.devtrends.com/wp-content/uploads/2010/01/StartupNotification.zip">source code</a> (written in Visual Studio 2008).</p>
<p>If you are interested in this application working in your environment, then there are a few things your must do…</p>
<ol>
<li>Create a SQL table with the necessary fields.</li>
<li>Update the application code with your connection string for the database.</li>
<li>Update the email notification messages.</li>
<li>Update the SMTP “from” email address.</li>
</ol>
<p>This is simple, I’ll help you along the way if you follow the steps below carefully:</p>
<p><em>SQL Database and Table</em></p>
<p>I use Microsoft SQL Server 2005; however, this could be easily ported to MySQL, another SQL database, or even a Microsoft Access database. I created a separate database named STARTUPNOTIFICATION for the purpose of holding the table. The table layout is simple, as shown below:</p>
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td width="213" valign="top">field name</td>
<td width="213" valign="top">data type</td>
<td width="213" valign="top">specifics</td>
</tr>
<tr>
<td width="213" valign="top"><strong>ID</strong></td>
<td width="213" valign="top"><strong>int</strong></td>
<td width="213" valign="top"><strong>primary key, no NULL</strong></td>
</tr>
<tr>
<td width="213" valign="top"><strong>Workstation</strong></td>
<td width="213" valign="top"><strong>nvarchar(50)</strong></td>
<td width="213" valign="top"><strong>NULL</strong></td>
</tr>
<tr>
<td width="213" valign="top"><strong>Date</strong></td>
<td width="213" valign="top"><strong>datetime</strong></td>
<td width="213" valign="top"><strong>no NULL</strong></td>
</tr>
<tr>
<td width="213" valign="top"><strong>IPaddress</strong></td>
<td width="213" valign="top"><strong>nvarchar(50)</strong></td>
<td width="213" valign="top"><strong>NULL</strong></td>
</tr>
</tbody>
</table>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/01/createtable.zip">createscript.sql</a></p>
<p>You must have a SQL user account that can SELECT and INSERT to this table. It is recommended that this account be a separate account and not the “sa” account or some other powerful, generic account.</p>
<p><em>Update Connection String</em></p>
<p>Locate the “mySQL.ConnectionString” property in the code, as shown the screen shot below, and update the string to match your server and database.</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/01/connectionstring.jpg"><img class="alignnone size-medium wp-image-501" title="connectionstring" src="http://www.devtrends.com/wp-content/uploads/2010/01/connectionstring-300x70.jpg" alt="connectionstring" width="300" height="70" /></a></p>
<p><em>Update Email Notification Messages</em></p>
<p>Locate the “sendEmail” function calls, as shown in the screen shot below, and update the strings to display the text you want to be included in the email. As an example, I have another application I wrote that grabs information on the workstation and user at sign-in; I place a link to display who has signed in to that workstation in the email for quick identification of the workstation location.</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/01/emailnotifications.jpg"><img class="alignnone size-medium wp-image-502" title="emailnotifications" src="http://www.devtrends.com/wp-content/uploads/2010/01/emailnotifications-300x132.jpg" alt="emailnotifications" width="300" height="132" /></a></p>
<p><em>Update SMTP “From” Address</em></p>
<p>The final line that needs your attention is the email address that is used as the sender for this application. In most cases you should use a “do not reply” type email address. Locate the sendEmail function near the bottom of the code and modify the msg.From property line, as shown the screen shot below.</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/01/smtpfromaddress.jpg"><img class="alignnone size-medium wp-image-503" title="smtpfromaddress" src="http://www.devtrends.com/wp-content/uploads/2010/01/smtpfromaddress-300x119.jpg" alt="smtpfromaddress" width="300" height="119" /></a></p>
<p>That is all, compile and set to run as in a startup script. For GPO startup scripts, the application could reside in the NETLOGON path as permissions have not been granted by a signed in user.</p>
<p>Aaron Gilbert</p>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/startup-notification/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Combine PDFs in Apple Automator</title>
		<link>http://www.devtrends.com/index.php/combine-pdfs-in-apple-automator/</link>
		<comments>http://www.devtrends.com/index.php/combine-pdfs-in-apple-automator/#comments</comments>
		<pubDate>Wed, 06 Jan 2010 02:38:17 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[AppleScript]]></category>
		<category><![CDATA[Automator]]></category>
		<category><![CDATA[Featured 2]]></category>
		<category><![CDATA[Folder Actions]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[.scpt]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=489</guid>
		<description><![CDATA[There is a dream that I will become specialized in one area, with my aspirations pointed towards network engineering. However, until I get there I will be good at everything, a jack-of-all-trades that seemingly cannot understand the meaning of “no”. This type of quality creates...]]></description>
			<content:encoded><![CDATA[<p>There is a dream that I will become specialized in one area, with my aspirations pointed towards network engineering. However, until I get there I will be good at everything, a jack-of-all-trades that seemingly cannot understand the meaning of “no”. This type of quality creates immediate opportunity that pays well but slows growth in specialized areas. My father always told me that I make too much to be rich, which I did not understand until I realized that my current efforts, which provide well, have a negative effect on crossing over into the entrepreneur realm.</p>
<p>Enough of that…today I step out of my Microsoft box and into the world of Apple and create some Automator workflows to combine multiple PDFs, in alphabetical order, from one folder and output into another folder.</p>
<p><strong>Apple Automator!</strong></p>
<p>The requirements of this task was to combine single page PDF files automatically, with the only user intervention being manual placement of the PDFs into a folder. This was accomplished with a two step process: (1) create the workflow in Automator; and (2) trigger the workflow using Apple’s Folder Actions feature.</p>
<p>For those that just want to steal, download <a href="http://www.devtrends.com/downloads/combinepdfs.zip">here</a>.</p>
<p>For me, this was the first time I have ever used Automator, and frankly, I was quite impressed with the ease and intuitive design. The basic process involved was to find the PDF files, sort that list, combine the PDFs, move that file to another location, add a date to the output file name and then remove the original list of PDF files.</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/01/fullworkflow.jpg"><img class="alignnone size-medium wp-image-490" title="fullworkflow" src="http://www.devtrends.com/wp-content/uploads/2010/01/fullworkflow-300x241.jpg" alt="fullworkflow" width="300" height="241" /></a></p>
<p>As shown in the screen shot above, this is easily accomplished using predefined Automator Actions, which includes the PDF actions. The first action is “Find Finder Items”, which allows us to specify a directory and other variables, such as file name specifics. In this case, I said all files that have a name that ends with .pdf. The second action is named “Sort Finder Items”, which takes the list as passed from “Find Finder Items”, notice the linking arrows between actions, and sorts them by name. The third will use the “Combine PDF Pages” action and will take the sort list of PDFs and make them into one PDF file.</p>
<p>The combined PDF is created in a temporary location with a random file name. The fourth action, “Move Finder Items” moves that file to a specific folder. Finally, the last in this group of actions is the “Rename Finder Items (Add Date or Time to Finder Item Names)”, which I have specified to add a date to the front of the file name.</p>
<p>The last two actions are in a different group for the reason that I do not want to work with the files/folders and provided by the last action in the previous group. Instead, similar to the start of the first group, the first action in this group is “Find Finder Items”, which allows us to specify a folder (same directory) and other variables, such as file name specifics. In this case, I said all files that have a name that ends with .pdf. The second and last action in this group moves those files to the Trash.</p>
<p><strong>Folder Actions</strong></p>
<p>Folder Actions employ AppleScripts that are triggered following a specific folder event. In this case and more common than not, the event is generally after files have been copied to the folder. As you may know, an AppleScript can accomplish a fair amount, including running Automator workflows and shell scripts. In this example, when we save the Automator workflow as a Plug-in, it will create two files, an application equivalent of the Automator workflow and an AppleScript that runs the Automator application.</p>
<p>To make an Automator workflow a folder action, you must save the Automator workflow as a “Plug-in”. At the Save As Plug-in dialog, you must choose ”Folder Actions” under “Plug-in for:” and then specify a folder to attach to. Name the plug-in accordingly and then click Save.</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/01/saveasplugin.jpg"><img class="alignnone size-medium wp-image-493" title="saveasplugin" src="http://www.devtrends.com/wp-content/uploads/2010/01/saveasplugin-300x156.jpg" alt="saveasplugin" width="300" height="156" /></a></p>
<p>Now that you have added an Automator workflow, right click on the folder and click on “Attach a Folder Action”. Choose the appropriate AppleScript – usually the same name as the Automator workflow you just saved.</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/01/whichscpt.jpg"><img class="alignnone size-medium wp-image-495" title="whichscpt" src="http://www.devtrends.com/wp-content/uploads/2010/01/whichscpt-300x208.jpg" alt="whichscpt" width="300" height="208" /></a></p>
<p>Assuming that Folder Actions are enabled and the script is set as a Folder Action to your folder, the Automator will run every time a file is copied into the folder.</p>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/01/folderactions.jpg"><img class="alignnone size-medium wp-image-494" title="folderactions" src="http://www.devtrends.com/wp-content/uploads/2010/01/folderactions-300x239.jpg" alt="folderactions" width="300" height="239" /></a></p>
<p><strong>Waiting until ALL Files are Copied</strong></p>
<p>If you copy more than one file at a time into the folder, you will likely run into an issue with this Automator/AppleScript combination where the script will begin before all files are completed copying. This is easily remedied by employing the following script example:</p>
<pre style="padding-left: 30px;">On adding folder items to this_folder after receiving added_items
  If folderReady(this_folder) then
    --((your Automator “tell” line goes here))
  End if
End adding folder items to

On folderReady(tFolder)
  Set myFolder to tFolder as alias
  Set firstSize to size of (info for myFolder)
  Delay 3
  Set newSize to size of (info for myFolder)

  Repeat while newSize ≠ firstSize
    Set firstSize to newSize
    Delay 3
    Set newSize to size of (info for myFolder)
  End repeat

  Return true
End folderReady</pre>
<p><a href="http://www.devtrends.com/wp-content/uploads/2010/01/combinepdfsscpt.jpg"><img class="alignnone size-medium wp-image-492" title="combinepdfsscpt" src="http://www.devtrends.com/wp-content/uploads/2010/01/combinepdfsscpt-300x245.jpg" alt="combinepdfsscpt" width="300" height="245" /></a></p>
<p>This script was taken from an example on <a href="http://macscripter.net/viewtopic.php?id=26564" target="_blank">MacScripter.Net</a>.</p>
<p>Aaron Gilbert</p>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/combine-pdfs-in-apple-automator/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>VCB Backup Script, vcbMounter</title>
		<link>http://www.devtrends.com/index.php/vcb-backup-script-vcbmounter/</link>
		<comments>http://www.devtrends.com/index.php/vcb-backup-script-vcbmounter/#comments</comments>
		<pubDate>Wed, 25 Nov 2009 06:13:22 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[Batch Files]]></category>
		<category><![CDATA[Featured 2]]></category>
		<category><![CDATA[VCB]]></category>
		<category><![CDATA[Backup]]></category>
		<category><![CDATA[vcbMounter]]></category>
		<category><![CDATA[VMware]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=474</guid>
		<description><![CDATA[Although the VMware Consolidated Backup (VCB) software receives considerable flack for lacking features or functionality, the fact that it provides you with access to the raw VMDK files is enough to alleviate all the missing functionality. As a side note, with Windows VMs, you can...]]></description>
			<content:encoded><![CDATA[<p>Although the VMware Consolidated Backup (VCB) software receives considerable flack for lacking features or functionality, the fact that it provides you with access to the raw VMDK files is enough to alleviate all the missing functionality. As a side note, with Windows VMs, you can also use the vcbMounter program to mount the VM at a file level &#8212; which eliminates the need for backup client software on the VMs.</p>
<p><strong>Full VMDK Backups</strong></p>
<p>The first thing I&#8217;d like to point out is that the vcbMounter does not &#8220;mount&#8221; the datastore of the VM as one might imagine. When using the vcbMounter program to access the VMDK files, vcbMounter only copies the files to your VCB server. For smaller VMs, 30GB or so, this really isn&#8217;t much of an issue; however, for the larger VMs, say 300GB, this could pose a problem.</p>
<p>At first, you may be frustrated as the vcbMounter program is a quirky program. Unless you are lucky or brilliant, give yourself some time to play with the command options.</p>
<p>For my purposes, I use the following vcbMounter statement:</p>
<pre style="padding-left: 30px;">vcbmounter -h [VC or HOST] -u [USERNAME] -p [PASSWORD] -M 1 -a name:[VM NAME] -r [LOCATION]\%1_vmdk</pre>
<p><strong>vmdkbkp.bat</strong></p>
<p>As expected, I am backing up many VMs; who would have an ESX(i) host with only one VM? (that is an entirely different subject I may address another day). Instead of scheduling the full vcbMounter command with each backup script or scheduled task, I created a batch file I use to extract the VMDK files. If you are interested in my script, <a href="http://www.devtrends.com/downloads/vmdkbkp.zip">download here</a>.</p>
<pre style="padding-left: 30px;">@ECHO OFF
rem ----------------------------------------------------------------
rem Batch file to create a "mount" dump of a VM, as specified in %1.
rem Created, 11/23/2009, Aaron Gilbert, www.devtrends.com
rem
rem Usage: vmdkbkp.bat [server name]
rem  e.g.: vmdkbkp MYSERVERNAMEINESX
rem
rem ----------------------------------------------------------------

rem check to make sure the user supplied an argument
if "%1"=="" goto error

rem format date in yyyy-mm-dd to apply to a directory, use is %today%
For /f "tokens=2-4 delims=/ " %%a in ('date /t') do (set today=%%c-%%a-%%b)

rem update the path statement so we can use vcbmounter
PATH=%PATH%;"C:\Program Files\VMware\VMware Consolidated Backup Framework\"

rem begin vcbmounter to copy the VMDK files down in file size (no 2GB split)
vcbmounter -h [VC or HOST] -u [USERNAME] -p [PASSWORD] -M 1 -a name:%1 -r [LOCATION]\%1_vmdk_%today%

rem lets finish up without error!
goto done

rem fail...
:error
echo.
echo Please provide one argument in the form of a VM server name, such as MYSERVERNAMEINESX
echo.

rem clean complete
:done</pre>
<p>The batch file requires only one argument, the name of the VM server you wish to backup. I use the batch file in conjunction with a non-traditional vcbMounter &#8220;mounting&#8221; approach. I &#8220;mount&#8221; with the intent of keeping that &#8220;mount&#8221; for an extended period of time. In addition to the vmdkbkp.bat file, I use another batch file to clean out the backup directory of all &#8220;mounted&#8221; directories older than a specific date. This allows me to keep the VMDK files on my backup-to-disk server for multiple weeks, while rotating out the older files.</p>
<pre style="padding-left: 30px;">@ECHO OFF
rem -------------------------------------------------------------
rem Batch file to remove all directories that are older than %1.
rem Created, 11/24/2009, Aaron Gilbert, www.devtrends.com
rem
rem Usage: clndir.bat [negative days]
rem  e.g.: clndir -3
rem
rem -------------------------------------------------------------

rem check to make sure the user supplied an argument
if "%1"=="" goto error

rem clean up the directory from all directories older than 3 days
forfiles.exe /p c:\backup\ /m *.* /d %1 /c "cmd /c if @isdir==TRUE rmdir /s /q @path"

rem lets finish up without error!
goto done

rem fail...
:error
echo.
echo Please provide one argument in the form of a negative number, such as -3
echo.

rem clean complete
:done</pre>
<p>Aaron Gilbert</p>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/vcb-backup-script-vcbmounter/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux Backup Shell Script</title>
		<link>http://www.devtrends.com/index.php/linux-backup-shell-script/</link>
		<comments>http://www.devtrends.com/index.php/linux-backup-shell-script/#comments</comments>
		<pubDate>Tue, 24 Nov 2009 06:39:39 +0000</pubDate>
		<dc:creator>aaron</dc:creator>
				<category><![CDATA[Shell Scripting]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[Backup]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://www.devtrends.com/?p=467</guid>
		<description><![CDATA[Once again, I am pushing my knowledge limits with the Linux world. The exciting part is that, the deeper I go, the better Linux gets. Today, it is Shell scripting, which quite frankly puts DOS batch files out of business. Wait a moment, which came...]]></description>
			<content:encoded><![CDATA[<p>Once again, I am pushing my knowledge limits with the Linux world. The exciting part is that, the deeper I go, the better Linux gets. Today, it is Shell scripting, which quite frankly puts DOS batch files out of business. Wait a moment, which came first?</p>
<p>Onward and upward, today I will share a script I use for backing files on my Ubuntu Server. To start off and to help explain what the script does, let me first explain my configuration. I have an ESXi 4.0 server with 3 VMs, all Ubuntu Server instances for Core services (DNS, DHCP, Directory services), Email (Yahoo&#8217;s Zimbra), and File (SAMBA shares, LAMP). All of the servers have 30GB primary partitions, provisioned through VMware and located on an internal 500GB SATA. The File server has a additional partition on the 500GB drive for all SAMBA shares and a partition located on an internal 1TB SATA drive that is used for backup.</p>
<p><a href="http://www.developingtrends.net/wp-content/uploads/2009/11/ESX-network.jpg"><img class="alignnone size-medium wp-image-472" title="ESX network" src="http://www.devtrends.com/wp-content/uploads/2009/11/ESX-network-300x272.jpg" alt="ESX network" width="300" height="272" /></a></p>
<p>The script provides backup functionality for my music, pictures, user&#8217;s folders, et cetera to the backup drive. The secondary partition on the 500GB drive is mounted to /datashare/ and the tertiary partition on the 1TB drive is mounted to /databackup/.</p>
<p><strong>The Script</strong></p>
<pre style="padding-left: 30px;">#!/bin/sh
#####
#  backup files script, version 1.
#
#  this script keeps one tar file per month for 12 months and rsyncs the entire contents
#  to $destination/daily_replica.
#  the idea is to run this script at least once per day to ensure proper sync and monthly tar.
#
#  created by Aaron @ www.devtrends.com
#####

# location of backup files (recursive sub-folders).
backup_files="/datastore/share"

# location to place tar files and /daily_replica/ directory.
destination="/databackup/share"

#### no editing beyong this line is required!
#### function for TARing
funcTar()
{
 options="--create --file="
 echo " -- tar'ing up $1 to $2/$3"
 echo "   \ creating new archive file: $3"
 tar $options$2/$3 $1
 echo "   \ tar backup completed."
}
#### end funcion

#### CREATE MONTHLY TAR FILE
# Create new archive filename.
month=$(date +%m)
year=$(date +%Y)
archive_file="backup-$month.tar"
full_path_archive_file="$destination/$archive_file"

# do I need to create a new monthly archive file?
# check if the file exists
if [ -f $full_path_archive_file ]; then

 # get file date
 filedate=$(stat -c %y $full_path_archive_file)
 # extract only the year of the file
 filedate=${filedate:0:4}

 # check if the file year is not the current year
 if [ ! $filedate == $year ]; then

 # remove old file
 rm $full_path_archive_file
 # create new tar
 funcTar $backup_files $destination $archive_file

 else

 echo " -- no tar'ing required today."

 fi

else

 # create new tar
 funcTar $backup_files $destination $archive_file

fi
#### DONE WITH TAR

#### rsync time...
echo " -- rsync $backup_files to $destination/daily_replica"
rsync -a $backup_files $destination/daily_replica
echo "   \ rsync completed."
####</pre>
<p>If you hate copy and paste, you may download the script <a href="http://www.devtrends.com/downloads/backup_share.zip" target="_blank">here</a>.</p>
<p><strong>Explanation</strong></p>
<p>For those that care, let me explain the script. The first two variables, $backup_files and $destination, should be the only variables you will need to change if you wish to use the script as I do.</p>
<pre style="padding-left: 30px;"># location of backup files (recursive sub-folders).
backup_files="/datastore/share"

# location to place tar files and /daily_replica/ directory.
destination="/databackup/share"</pre>
<p>The next block of code is the function used to create the tar file. The reason I made it into a function is because I use the same block of code twice in the main section of the script. No reason to duplicate code. However, the way I implemented the function may cause a problem if your $backup_files or $destination variables contain spaces. If anyone would like to revised, please share. The $1, $2, et cetera, are the argument variables as passed by the calling statement.</p>
<pre style="padding-left: 30px;">funcTar()
{
 options="--create --file="
 echo " -- tar'ing up $1 to $2/$3"
 echo "   \ creating new archive file: $3"
 tar $options$2/$3 $1
 echo "   \ tar backup completed."
}</pre>
<p>Next I define some variables that I will use throughout the script. The first two are date strings that contain the month (e.g. 11) and the year (e.g. 2009). The third and fourth variables hold the location of the tar file. The tar file name is comprised of the word &#8220;backup-&#8221; and then the variable of the month.</p>
<pre style="padding-left: 30px;">month=$(date +%m)
year=$(date +%Y)
archive_file="backup-$month.tar"
full_path_archive_file="$destination/$archive_file"</pre>
<p>The main section of code is next and consists of a nested if statement. The first if statement checks if a current backup tar file exists, if not, then it will create one, otherwise it will check the status of the current backup tar file. If the current tar file modified date is not in the current year, then it removes that tar file and recreates it, otherwise there is no need to tar anything. The most interesting piece is the substring command, ${filedate:0:4} which only returns characters 0,1,2,3 from the variable $filedate. You will also notice the use of stat, which, depending on your distribution of Linux, you may need to manually acquire.</p>
<pre style="padding-left: 30px;"># do I need to create a new monthly archive file?
# check if the file exists
if [ -f $full_path_archive_file ]; then
 # get file date
 filedate=$(stat -c %y $full_path_archive_file)
 # extract only the year of the file
 filedate=${filedate:0:4}

 # check if the file year is not the current year
 if [ ! $filedate == $year ]; then
  # remove old file
  rm $full_path_archive_file
  # create new tar
  funcTar $backup_files $destination $archive_file
 else
  echo " -- no tar'ing required today."
 fi

else
 # create new tar
 funcTar $backup_files $destination $archive_file
fi</pre>
<p>The last statement is the rsync command which synchronizes the entire content of the $backup_files location to the $destination/daily_replica/ location.</p>
<pre style="padding-left: 30px;">echo " -- rsync $backup_files to $destination/daily_replica"
rsync -a $backup_files $destination/daily_replica
echo "   \ rsync completed."</pre>
<p>Use this script at your own risk. If it turns out it didn&#8217;t back up your stuff, that is entirely your problem, not mine.</p>
<p>-Aaron Gilbert</p>
]]></content:encoded>
			<wfw:commentRss>http://www.devtrends.com/index.php/linux-backup-shell-script/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
