Tuesday
Oct092012

Duty and Solitude

So I'm taking a weekly short story writing workshop at the Virginia Museum of Fine Arts, which was to be a painting class but at the last moment when I saw the opportunity to write a little fiction, I was compelled to run with instead.  I'm sort of loving it now. This week's short story was the result of being told to write a story about loneliness.

Duty and Solitude

   It has been many mortal lifetimes since last I spoke to another, those who knew my voice, my touch, the smell of my hair, have long since returned to dust to begin their great journey again. This is the cost of the oath I have sworn to uphold, for I am the Toth Farah, the chosen one. The Farah is all that stands between the things of beauty and grace in our world and the ancient horrors, writhing with tentacles and bereft of eyes, that will one day rise to reclaim the Earth and return it to the darkness from the time of its birth. Until then, the whole of this world is my prison where I walk among you, neither seen nor heard.

   I was not always as I am today, I too was once mortal before I accepted the burden that I was born to bear and chose to perform the sacred rite of the Farah. The memories of that life, thousands of years gone now, remain ever sharp. I drift in hauntingly clear visions of my longhouse, filled with my children, their laughter the soft sound of raindrops on bells, their small, cool hands held tightly in mine as we walked at dawn in the mist filled meadows.  My sisters and I beckoning the sun to rise with a song, and drinking the moon to sleep each night, all there in perfect clarity, yet gone forever. My beloved husband wore the title and trappings of warlord and chieftain and for our perfect days together he was, of the two of us, the one that knew and embraced battle; I cared nothing for his brutal art, the murals of red and the sadness each stroke bore on black crow wings.  Yet ages later, it is I, not he, who is destined to lead an army and, though the coming battle has always weighed heavily on my heart, I will not waver in what must be done in duty or sacrifice.  None may hear my voice or know that I walk among you, or the rite will be broken and I would fade from this world with no one to carry my mantle.

   I walk freely among all the people of the world and neither door of steel nor wall of stone may bar my passage as I fulfill my duty and gather the discarded teeth of man's young. I have done so for years beyond counting, bearing silent witness to the rise and fall of kingdoms.  My work continues with all the love, passion and drama of the ages denied me as I garner my forces. When that dark and prophesied day comes, I shall draw them forth and sow them as tiny white seeds of hope in the rich black earth where the dazzling light of our sun will bear witness to our gambit.  From the teeth of the innocent our greatest army will spring from the Earth to drive back and imprison despair’s foul forces, securing the hope and prosperity of mankind until the wheel of time spins again and all of these events begin anew, with another Toth Farah chosen to prepare for battle across the ages to come as have I and those that came before me.

   Until that fateful day, my ceaseless work remains fodder for your legends, your name for me ever on your lips but my sacrifice, and depth of solitude, unknown to you.  So leave still the teeth of your youth beneath their headrests and one day I shall stand by your side on the ashes of that vanquished army of the dark where I may once again share your wine and share with you all the full tale of your Tooth Fairy.

 

Thursday
May242012

Making SharePoint look like a million bucks on a shoestring budget - Part II

AKA the sexy, animated, customizable alerts bar you can have for 6 bucks.

In the second installment of our using jQuery, SPS and some inexpensive UI controls we implement a beautiful, customizable alerts/notification bar for SharePoint, driven off of a SharePoint list that'll set you back $6 and take your lunch hour to implement.  Very little configuration is needed to get you up and rolling here and, staying true to a tennant of this series, there is no requirement to install any invasive files (solutions) on your server or farm.  

As shown here it will handle multiple alerts being active at the same time and will default to a generic theme if there is more than one alert.  What you do not see here is the slick behavior of this notification bar and the options it exposes, see a demo of all that the bar does here.

 
First a few Resources needed:
  • The notification bar ($6), officially named 'FooBar - A jQuery Notification Bar'
  • jQuery (consider putting this reference in your master page)
  • SPServices (also consider putting this reference in your master page) 
SharePoint List:
I created a simple custom list (System Alerts) with the following columns, if you use other names just reflect that in lines 36-38, if you don't know the ows_X field name uncomment line 23 to see.
  • IsActive - a yes/no data type
  • Theme - String datatype, required - I made this a choice and provided canned options
  • Message - Multiple lines of text datatype, plaintext or not
The code below:
This I dropped into a text file and just added to a page via a CEWP letting me choose on which pages this is displayed, in our case just the home page.  Note the mention of an image as part of the theme, you can whip your own up or just remove the text in line 85 (and comma in 84 then).  The screenshot above shows how nice this looks with a good image though.
<style> <!-- A few canned styles, go nuts here -->
	.foobar_darktext{ font-weight:bold; color:black; }
	.foobar_lighttext{ font-weight:bold; color:white; }	
</style>
<!-- For jQuery -->
	<script type="text/javascript" src="/_layouts/1033/jquery.SPServices.min.js"></script>
	<script type="text/javascript" src="/_layouts/1033/jquery.min.js"></script>
<!-- For Foobar -->
	<script type='text/javascript' src='/_layouts/1033/jquery.foobar.min.js'></script>
	<link href='/_layouts/1033/styles/jquery.foobar.css' type='text/css' rel='stylesheet' />

<script language = "javascript">
$(document).ready(function() 
{
	$().SPServices(
	{
		operation: "GetListItems",
		webURL: "https://YOUR_SHAREPOINT_SITE",    
		async: false,
		listName: "YOUR_LIST_NAME",
		completefunc: function (xData, Status) 
		{
			//alert("Response from server: " + xData.responseText); // helpful for debug
			var output = "";
			$("#WSOutput").html(output);
			
			var iCounter = 0;
			var arrPayload = [];
			var szBackHexColor = '';
			var szAppliedStyle = '';
			var szPathAlertImage = 'https://SOME_SHAREPOINT_PATH/AlertImages/';
			var szFullPathAlertImage = '';
			
			$(xData.responseXML).find("[nodeName='z:row']").each(function() { 
				
				var szContent = ($(this).attr("ows_Message"));				
				var bIsActive = ($(this).attr("ows_IsActive"));
				var szTheme = ($(this).attr("ows_Theme"));				
				
				if(bIsActive == 1)
				{				
					arrPayload.push(szContent);
							
					switch(szTheme) // set theme options
					{
						case 'Theme A':
							szBackHexColor = '#ffcc00';
							szAppliedStyle = 'foobar_darktext';
							szFullPathAlertImage = szPathAlertImage + 'Achtung.png';							
							break;
						case 'Theme B':
							szBackHexColor = '#CAF87A';
							szAppliedStyle = 'foobar_darktext';
							szFullPathAlertImage = szPathAlertImage + 'Danger Will Robinson.png';	
							break;							
					}					
					szFullPathAlertImage = '<img style=\' padding:0px 0 0 7px; \' src= \'' + szFullPathAlertImage + '\' />';
					iCounter++;  // increment
				}
			});
			
			if(iCounter > 1) // to prevent conflicting themes, make this a generic visual
			{
				szBackHexColor = '#ffcc00';
				szAppliedStyle = 'foobar_darktext';
				szFullPathAlertImage = szPathAlertImage + 'Generic Alert.png';			
			}
			
			if(iCounter > 0) 
			{
				$.foobar({
					"positioning" : "fixed",
					"display" : "delayed",
					"displayDelay" : 2000,
					"messagesDelay" : 5000,
					"messagesScrollSpeed": 50,
					"messagesScrollDelay": 2000,
					"messageClass" : szAppliedStyle,
					"fontColor" : szFontHexColor,
					"backgroundColor" : szBackHexColor,
					"buttonTheme" : "long-arrow",
					"height" : 35,
					"enableShadow" : false,
					"messages" : arrPayload,
					"leftHtml": szFullPathAlertImage
				});	
			}
		}
	});
});	
</script>
<div id="x_container" />
Further fiddling:
Initially I set this up so that the user could specify the background color, the text color, select an image or provide a URL, etc. All of which are done the same way as the theme logic but we decided to simplify the user experince with themes rather than give them so many choices.  You can easily expand on this though.
Consider throwing a few bucks at the jQuery and/or SPServices teams via the donate links on their sites, they do good work and deserve some help. I am not affiliated with any of the groups/sites I steer you toward in these posts...

 

 

Thursday
May102012

Using SPServices, jQuery and an $8 Tab Plugin to Create a Stunning SharePoint List Visualization

AKA: Making SharePoint look like a million bucks on a shoestring budget - Part I

I'd been wanting to spend some time getting familiar with the brilliant open source jQuery library for SharePoint's web services, SPServices, and had the opportunity this past week.  We had a SharePoint site under construction that needed to have a list of links and a little explanation to accompany them  and initially just hosted a standard grouping mechanism on the list view which looks terrible.  I thought that this might be a chance to offer a better way to visualize list data using an inexpensive tabbing control in my library.  The result is shown below, with each 'tab' here linking to 3 fields from a custom list, the title, the sub-title and the content.  This stays in our needed model of users interacting with the standard SP forms that they understand and is leaps and bounds a better user experience.  If this takes you more than a lunch break to implement you might need longer lunch breaks, this is an easy setup and looks great, even responding to mouse-wheel scrolling through the tabs.

 

To do this I used a stunning jQuery plugin called 'Sliding Tabs' that I bought from CodeCanyon.net for a dirt cheap 8 bucks (authored by 360north).  You can see/buy it here and it is one of the many ridiculous bargains on that site that I'll be extending in future posts.  That 8 dollars is all this costs and it looks like $1,000,008 when done.  You'll also need to grab the jQuery file and the SPServices file, we have these hosted on our farm and embedded in the master page for ease of use as seen in the includes in the code.  All includes that are needed but the core jQuery and SPServices files come with the Sliding Tabs package.

With the code in hand from them I created a new list with the aforementioned title, subtitle and content (being a rich content type) list and hooked it up.  Full code follows below.

Notice in the code that you have to specify your site and list names (lines 13 and 15) and if you name your columns something other than I did, you'll need to change the mappings to the 'ows_XXX' moniker that matches your fields (lines 26, 28 and 30).  If you are unsure of what your field's names are you can uncomment line 20 and see for yourself.

You can change the size, shape, orientation and other options with the tab control with the options section starting at line 58.  It does not handle overflow well (um, not at all really, it just gets clipped) but for what we needed it was perfect.

 

<script type="text/javascript" src="_layouts/1033/jquery.min.js"></script>
<script type="text/javascript" src="_layouts/1033/jquery.SPServices.min.js"></script>
<script type="text/javascript" src="_layouts/1033/jquery.mousewheel.js"></script>
<script type="text/javascript" src="_layouts/1033/jquery.slidingtabs.pack.js"></script>
<script type="text/javascript" src="_layouts/1033/jquery.easing.js"></script>

<link href="_layouts/1033/styles/slidingtabs-vertical.css" rel="stylesheet" type="text/css" media="screen"/>

<script language = "javascript">
$(document).ready(function() {
	$().SPServices({
		operation: "GetListItems",
		webURL: "https://MY_SHAREPOINT_ROOT/SOME_SITE",    
		async: false,
		listName: "SIMPLE DEMO LIST",
		completefunc: function (xData, Status) {

			//alert("Status of XML message reaching Sharepoint webservice: " + Status); // handy for debug
			//alert("Response from server: " + xData.responseText); // also handy for debug
			var output = "";
			$("#WSOutput").html(output);
			var TabCounter = 0; // to keep track of tabs/containers

			$(xData.responseXML).find("[nodeName='z:row']").each(function() {

				var title = ($(this).attr("ows_Title"));
					if(title.length == 0) {title = ' ';}
				var subtitle = ($(this).attr("ows_Subtitle"));
					if(subtitle.length == 0) {subtitle = ' ';}
				var content = ($(this).attr("ows_Content"));
					if(content.length == 0) {content = ' ';}
				 
				var TabClass = 'st_tab';
				var ContainerClass = 'st_tab_view';
						
				if(TabCounter==0) // our first items get special 'active' class styling
				{				
					TabClass = 'st_tab st_tab_active';
					ContainerClass = 'st_tab_view st_first_tab_view';
				}
				// Create new tab element as <li>
				var TabHeader = '<li><a href="#stv_content_' + TabCounter + '" rel="v_tab_' + TabCounter + '" class="' + TabClass + '">'
					TabHeader += title + '<span>' + subtitle + '</span></a></li>';
				$("#tablisting").append(TabHeader); // insert into DOM

				// Create new lined content container as <div>
				var stv_content = '<div id="stv_content_' + TabCounter + '" class="' + ContainerClass + '" style="overflow:auto;">';
					stv_content +='<h2 class="sc_title">' + title + '</h2><div class="text"><p>' + content + '</p></div></div>';			
				$("#st_tabs_container").append(stv_content); // insert into DOM

				TabCounter++;  // increment counter
			});
		}
	});
	   
	// Instantiate Vertical Sliding Tabs
	$('div#st_vertical').slideTabs({  			
		// Options
		contentAnim: 'slideH',
		contentAnimTime: 600,
		contentEasing: 'easeInOutExpo',
		orientation: 'vertical',
		tabsAnimTime: 300,
		totalWidth: '660',		
		tabScroll: true				
	});
});	
</script>
       
<!-- Start HTML - Vertical tabs -->
<div id="st_vertical" class="st_vertical">
	<div class="st_tabs_container">   
		<a href="#prev" class="st_prev"></a>
		<a href="#next" class="st_next"></a>                                                                    
		<div class="st_slide_container">            
			<ul class="st_tabs" id="tablisting"></ul><!-- tabs inserted here -->               
		</div> <!-- /.st_slide_container -->
	</div> <!-- /.st_tabs_container -->                               
	<div class="st_view_container">        	            
		<div class="st_view" id="st_tabs_container"></div> <!-- content blocks inserted here -->                        
	</div> <!-- /.st_view_container -->
</div> <!-- /#st_vertical -->
<!-- End HTML - Vertical tabs -->
More jQuery visual enhancements to come...
Wednesday
Apr252012

SharePoint and jQuery - Get the Authenticated User's Name and Modify Content Client-Side

Getting the user's name from a SharePoint page and handing off to some client-side code proved messier than I had hoped for.  Part of the problem is that our farm runs on SSL and IE likes to throw the error message when some resources are secure and others are not and the common solution to this (using jquery.SPServices) was falling prone to that error.  Instead I worked around it so we could serve up a custom control on the intranet for someone's 50th birthday and manage it without audiencing controls.

It requires jQuery and the control that contains the username was not always assigned the same ID though it has the same AccessKey attribute so we made do with what we had.

Since this elegant method did not work:
$(document).ready(function() {
	alert($().SPServices.SPGetCurrentUser());
});

we instead went with the following (note that this includes the resulting action). If you are not in an HTTPS environment go with the SPServices option...

<script type="text/javascript">
	$(document).ready(function() {
		var str = '';
		var strContainer = '';
		
		// find the container that contains the user's name (not always the same...)
		if($('#zz14_Menu').attr("accesskey") == 'W') {strContainer = 'zz14_Menu';}
		if($('#zz15_Menu').attr("accesskey") == 'W') {strContainer = 'zz15_Menu';}
		if($('#zz16_Menu').attr("accesskey") == 'W') {strContainer = 'zz16_Menu';}
		if($('#zz17_Menu').attr("accesskey") == 'W') {strContainer = 'zz17_Menu';}
		
		var str = $('#' + strContainer).find("span").text();

		// date driven
		var d = new Date();
		var n = d.getDate();
		if(n==26) // only show on the 26th...
		{
			if(str == 'Jane User')
			{
				$('#mycontrol').attr('src', 'https://server/Rotator/alternative.htm');
			}
		}
	});
</script>
Friday
Mar302012

PowerShell - Use an XML File for Script Settings and Logging

One of the guys I work with was looking to automate some software installation on new machines, sort of location-specific modifications to a firm-wide image, and wanted a way to have a script run steps, reboot and restart at the next step. He was effectively looking for a way to maintain information and state between times that the script ran. I thought using a local XML file to store states and log events into, treating it as a database that could remain on the machine afterwards for reference in troubleshooting might work well for him and created some code he could use.

I wound up building a few functions that would allow his script to create and interact with the local XML file so that he could both store and retrieve relevant values and log events along the way. This is broken down into functions that can be placed in a single script or that you can place in other files, whatever floats your boat.

Example of file

With the functions below you can:
  • Read and write status step value
  • Create, read and update configuration-params
  • Write only to log entries with some activities being auto logged
<setup>
  <status step="3" />
  <configuration>
    <param type="script version">1.0</param>
    <param type="ip address">129.168.1.108</param>
  </configuration>
  <log>
    <entry timestamp="03/20/2012 14:33:34">Log created</entry>
    <entry timestamp="03/20/2012 14:33:34">W00t!</entry>
    <entry timestamp="03/20/2012 17:18:44">Config/Param update. Old: 129.168.1.17, New: 129.168.1.18</entry>
    <entry timestamp="03/20/2012 17:23:01">Error, attempted to update step in log file to a blank value.</entry>
    <entry timestamp="03/20/2012 17:41:10">Stage value update. Old: 2, New: 3</entry>
  </log>
</setup>

Main - Control flow from a single function

Example of setting the script up and how to call the functions;
## Load Location-based Variables
##############################################################################
## Declare variables with :Script scope
##############################################################################

## Need Configured
	$Script:scriptversion = "1.0"
	$Script:logfilefolder = "C:\temp"	# will be created if does not exist
	$Script:logfilename = "C:\temp\MachineSetupLog.xml"	# will be created if does not exist

## General Use
	$Script:currentstep = "Error"  # This variable will hold your current step counter if needed in IF/THEN

##############################################################################	
## Control - Main
##############################################################################
	Function Main{	
	
		## initialize log and get step value (in case of restart)
		PrepLogFile
		GetStepFromLog
		
		## updates the step value in the log and the script variable $currentstep, both at once
		UpdateStepInLog("2")
		
		## add arbitrary text to log as an entry, timestamp added by function
		#AddEntryToLog("W00t!")
		
		## add param entry to log will overwrite if already exists, logging old and new values
		##  format is: AddConfigurationEntry <param type> <param value> <log entry (1/0)>
		##	The third parameter is a 0=false 1=true setting if the change should show old/new values in the log
		##    set this to 0 when removing or changing a sensitive item such as a password.
		AddConfigurationEntry "ip address" "192.168.0.1" 1
		
		## read parameters back: $foo = GetConfigurationEntry("IP Address")
		#PopUp(GetConfigurationEntry("ip address"))
		#PopUp(GetConfigurationEntry("script version"))
		
		##Done
		PopUp("Main Complete")
	}

Popup Creator Function

For debug really, function for popping up an alert to notify of some value/state
##############################################################################
## function PopUp - pops a message to user
##  usage: PopUp(GetConfigurationEntry("ip address")) 
##  usage: PopUp("More Coffee!")
##############################################################################
	Function PopUp([string]$messageIn){
		$a = new-object -comobject wscript.shell
		$b = $a.popup($messageIn,0,"CBH Script Alert",1)
	}

Exit Script Function

This was provided to call when an error state was detected or the need to shut the process down before the natural end occured, it logs the event and the message you pass to it to the log.
##############################################################################
## function ExitScript - Logs message and ends execution [ARG-STRING]
##  usage:  ExitScript("1 cannot = 2, aborted.")
##############################################################################
Function ExitScript($logmessage){
	$endingmessage = "Ending Execution with status: " + $logmessage
	AddEntryToLog($endingmessage)
	exit
}

Creates folder and log if they do not exist

Note the 2 string variables in the main function used here to specify the folder and file path. This also creates the blank XML template with a few values filled in. Mod as needed.
##############################################################################
## function PrepLogFile - Creates folder and log if they do not exist
##  usage: PrepLogFile
##  usage notes: be sure to set values of $Script:logfilefolder and $Script:logfilename beforehand
##############################################################################
Function PrepLogFile{
#Create folder if needed
	if (!(Test-Path -path $logfilefolder))
	{
		New-Item $logfilefolder -type directory
	}
	## Create our temp file if needed (with content, elements on different lines for readability later)
	if(!(Test-Path $logfilename))
	{
		$date = Get-Date
		$blanklog = "<entry timestamp='" + $date + "'>Log created</entry>"
		$blankparam = "<param type='script version'>" + $scriptversion + "</param>"

		add-content $logfilename "<setup>"
		add-content $logfilename "<status step='0' />"
		add-content $logfilename "<configuration>"
		add-content $logfilename $blankparam	
		add-content $logfilename "</configuration>"
		add-content $logfilename "<log>"
		add-content $logfilename $blanklog
		add-content $logfilename "</log>"
		add-content $logfilename "</setup>"
		$logfilename.Save($logfilename)
	}
}

Pulls current step from log file

Calling this reads the value from your file and sets the script-level variable $Script:currentstep to the value of the step.
##############################################################################
## function GetStepFromLog - Pulls current step from log file into $Script:currentstep
##  usage: GetStepFromLog
##############################################################################
	Function GetStepFromLog{
		$xml = [xml] (Get-Content $logfilename)		#open file
		$Script:currentstep =  $xml.setup.status.GetAttribute("step")	#map currentstep to XML step value
	}

Updates the step value

This sets the step in your file and updates the script-level variable $Script:currentstep while it is at it. It also makes a timestamped log entry of this change, reflecting the old and new values.
##############################################################################
## function UpdateStepInLog - Set step to new value log file [ARG-STRING]
##  usage: UpdateStepInLog("42")
##############################################################################
	Function UpdateStepInLog($newstep){	
		if($newstep -ne "")
		{
			#Open xml file and change step to newstep value
			$xml = [xml] (Get-Content $logfilename)		#open file
			$status = $xml.setup.status		#map $status to that element in XML
			$status.step = $newstep		# set value
			$xml.Save($logfilename)		#save			
			GetStepFromLog		# Refresh $Script:currentstep from log we just changed
			$logmessage = "Stage value update. Old: " + $oldstep + ", New: " + $newstep
			AddEntryToLog($logmessage)
		}
		else
		{
			PopUp("Error, trying to update step in log file to a blank value")
			AddEntryToLog("Error, attempted to update step in log file to a blank value.")
		}
	}

Make log entry

This creates a timestamped entry in the log portion of the XML file for you. Use liberally.
##############################################################################
## function AddEntryToLog - Adds a new 'entry' node and datestamp to log file [ARG-STRING]
##  usage: AddEntryToLog("Look at me still logging when there's science to do.")
##############################################################################
	Function AddEntryToLog($message){
		$date = Get-Date	#for datestamp
		$xml = [xml] (Get-Content $logfilename)		#open file
		$newEntry = $xml.CreateElement("entry")		#create new entity 'entry'
		$newEntry.set_InnerXML($message)		#insert the message
		$newEntry.SetAttribute("timestamp",$date)	#populate timestamp attribute
		$xml.setup.log.AppendChild($newEntry)	#append to <log> entity
		$xml.Save($logfilename)		#save
	}

Add a configuration parameter

This will update the value if it exists already (and is a change) or add and entry if this is new. Additionally, it logs the old/new values when it updates an entry UNLESS you tell it to supress logging of the old, new values. This was added as the need to use these parameters for sensitive information might arise and though they may be purged at the end of the script, would remain in the log unless we allowed suppression.

If you call this with the log bit set to 0 it will supress logging the old/new value. If you pass a 1 it will log all details of your change. This is recommended.

##############################################################################
## function AddConfigurationEntry - Adds a new 'parameter' node and configuration portion of log file [ARG-STRING], [ARG-BOOL]
##  usage (standard): AddConfigurationEntry "ip address" "129.168.1.1" 1
##  usage (supress): AddConfigurationEntry "secret value" "foobar!" 0
##############################################################################
	Function AddConfigurationEntry{
		param([String]$paramtype, [String]$paramtext, [Bool]$logchange) # map parameters to variables
			# the boolean value '$logchange' will suppress the log entry when set to false
		$xml = [xml] (Get-Content $logfilename)		#open file
		
		#already an item?
		$logatend = "no"		# flag to log after we close the file
		$logmessage = ""	# holder in case we need to log
		$test = $xml.setup.configuration.param | where { $_.type -eq $paramtype} #look for pre-existing param
		if($test)
		{
			# already exists
			# only update if they are not the same value
			if($test.InnerXML -ne $paramtext) #test for inequality
			{
				# prepare to log this change, storing the old value and the new one
				if($logchange) # only do this if $logchange function param is true
				{
					$logmessage = "Config/Param update (" + $paramtype + "). Old: " + $test.InnerXML + ", New: " + $paramtext							
				}
				else # generic message
				{
					$logmessage = "Config/Param update (" + $paramtype + "). Values supressed."
				}
				$logatend = "yes"
				#set and save the change
				$test.InnerXML = $paramtext	# set value
				$xml.Save($logfilename)		#save				
			}
		}
		else # OK to add
		{
			$newEntry = $xml.CreateElement("param")		#create new entity 'param'
			$newEntry.set_InnerXML($paramtext)		#insert the message
			$newEntry.SetAttribute("type", $paramtype)	#populate type attribute
			$xml.setup.configuration.AppendChild($newEntry)	#append to <configuration> entity
			$xml.Save($logfilename)		#save		
		}
		if($logatend -eq "yes")
		{
			# logging was not working while the file was open, we moved it to the end here and it works.  boom.
			AddEntryToLog($logmessage)
		}
	}