


      <rss version="2.0">
         <channel>
            <title><![CDATA[ Pacific NW Data Consultants Blog]]></title>
            <link>http://www.PNWDC.com</link>
            <description>Pacific NW Data Consultants is a private computer consulting firm serving all of your technology needs.</description>
            <copyright>Copyright 2009 Pacific NW Data Consultants, LLC</copyright>
   
      <item>
         <title><![CDATA[Accordion FAQ with Searching and Highlighting]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p>
	Recently I was working on a website redesign and one of the items that was specified in the
	requirements was a Frequently Asked Questions (FAQ). These are pretty run of the mill these 
	days. In the design they showed a search box for the FAQ in addition to the full site-wide 
	search. They also wanted collapsible (accordion) sections with collapsible questions within 
	each section. Aside from the search, not a biggie.
</p>

<p>
	Now, normally I would go back to the customer and seek some clarification, but this time I 
	decided I would try something, just to see if I could do it. I didn't want to implement a 
	server-side solution to the searching, since everything was on this page and already loaded.
	I figured there would be a way to accomplish all of my goals with a little bit of JavaScript 
	and the handy <a href="http://www.jquery.com">jQuery</a> library. As it turns out it was not 
	as easy as I thought it would be, yet the amount of code that it required was still incredibly 
	minimal.
</p>


<h2>The FAQ structure</h2>

<p>
	The structure of the FAQ that I was provided was pretty basic, nothing fancy or out of the 
	ordinary to see here.
</p>

<pre>
&lt;div&gt;
	&lt;label for="FAQSearch"&gt;Frequently Asked Questions Search&lt;/label&gt; 
	&lt;input type="text" name="FAQSearch" id="FAQSearch" /&gt;
	&lt;input type="submit" id="SearchFAQ" value="search" /&gt;
&lt;/div&gt;

&lt;div&gt;
	&lt;a id="ExpandAll" href="javascript:void(0);"&gt;Expand All&lt;/a&gt; / 
	&lt;a id="CollapseAll" href="javascript:void(0);"&gt;Collapse All&lt;/a&gt;
&lt;/div&gt;

&lt;div id="FAQ"&gt;
	&lt;h3 class="Topic"&gt;Topic 1&lt;/h3&gt;
	&lt;div class="TopicContents" style="display: none;"&gt;
		&lt;h4 class="Question"&gt;Question #1-1&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #1-1&lt;/div&gt;
		&lt;h4 class="Question"&gt;Question #1-2&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #1-2&lt;/div&gt;
		&lt;h4 class="Question"&gt;Question #1-3&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #1-3&lt;/div&gt;
		&lt;h4 class="Question"&gt;Question #1-4&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #1-4&lt;/div&gt;
		&lt;h4 class="Question"&gt;Question #1-5&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #1-5&lt;/div&gt;
		&lt;h4 class="Question"&gt;Question #1-6&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #1-6&lt;/div&gt;
	&lt;/div&gt;
	&lt;h3 class="Topic"&gt;Topic 2&lt;/h3&gt;
	&lt;div class="TopicContents" style="display: none;"&gt;
		&lt;h4 class="Question"&gt;Question #2-1&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #2-1&lt;/div&gt;
		&lt;h4 class="Question"&gt;Question #2-2&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #2-2&lt;/div&gt;
		&lt;h4 class="Question"&gt;Question #2-3&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #2-3&lt;/div&gt;
		&lt;h4 class="Question"&gt;Question #2-4&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #2-4&lt;/div&gt;
		&lt;h4 class="Question"&gt;Question #2-5&lt;/h4&gt;
		&lt;div class="Answer" style="display: none;"&gt;Answer #2-5&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;
</pre>

<p>
	Like I said, pretty run of the mill stuff. But what this lacks is functionality to 
	actually perform the searching and expanding. Lets take a look at the JavaScript 
	to see what I did.
</p>


<h2>The script that makes it all happen</h2>

<pre>
&lt;script&gt;
	$('h3.Topic').click(function () {
		$(this).next().toggle(300);
	});
	$('h4.Question').click(function () {
		$(this).next().toggle(300);
	});
	$('#ExpandAll').click(function () {
		$('#FAQ').children('div.TopicContents').show(300).children('div.Answer').show(300);
	});
	$('#CollapseAll').click(function () {
		$('#FAQ').children('div.TopicContents').hide(300).children('div.Answer').hide();
	});
	jQuery.expr[':'].Contains = function (a, i, m) {
		return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) &gt;= 0;
	};
	$('#SearchFAQ').click(function () {
		$('#FAQ').children('div.TopicContents').hide().children('div.Answer').hide();
		if ($('#FAQSearch').val() != '') {
			$('div.Answer:Contains(' + $('#FAQSearch').val().toUpperCase() + ')').show().parent().show(300);
			try {
				$('.highlight').removeClass("highlight");
				$('div.Answer:Contains(' + $('#FAQSearch').val().toUpperCase() + ')').each(function () {
					$(this).html(
						$(this).html().replace(
							new RegExp($('#FAQSearch').val(), "ig"), 
							function(match) {
								return '&lt;span class="highlight"&gt;' + match + '&lt;/span&gt;';
							}
						)
					)
				});
			}
			catch (err) {
			}
		}
		return false;
	});
&lt;/script&gt;
</pre>

<p>
	Lets break this up into individual functions.
</p>

<h2>The Accordion Effect</h2>
<pre>
	$('h3.Topic').click(function () {
		$(this).next().toggle(300);
	});
</pre>

<p>
	This should be pretty straight forward. When the user clicks on a <em>Topic</em> it toggles the
	visibility of the next tag, which should always be a div with class <em>TopicContents</em>.
</p>


<pre>
	$('h4.Question').click(function () {
		$(this).next().toggle(300);
	});
</pre>

<p>
	This is almost identical to the code before, except this toggles the element following 
	an h4 with class <em>Question</em>. This should be a div with class <em>Answer</em>.
</p>

<pre>
	$('#ExpandAll').click(function () {
		$('#FAQ').children('div.TopicContents').show(300).children('div.Answer').show(300);
	});
</pre>

<p>
	When the user clicks the <em>Expand All</em> link at the top this will set all divs with 
	the class <em>TopicContents</em> to visible and then set all child divs with class <em>Answer</em>
	to visible.
</p>

<pre>
	$('#CollapseAll').click(function () {
		$('#FAQ').children('div.TopicContents').hide(300).children('div.Answer').hide();
	});
</pre>

<p>
	Much like the previous item but in reverse. This will set the visibility of all divs with 
	class of <em>TopicContents</em> or <em>Answer</em> to hidden.
</p>

<h2>Searching</h2>

<pre>
	jQuery.expr[':'].Contains = function (a, i, m) {
		return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) &gt;= 0;
	};
</pre>

<p>
	This was a major piece of the magic that made the searching work. This adds a special 
	selector, :Contains, that will match with case insensitivity. It does this by converting 
	the text to search to upper case and the text you are searching for to upper case and then 
	performs the actual comparison. I did not write this but do not recall which site I had 
	pulled this off of. I believe it was on the jQuery forums or Stack Overflow.
</p>

<pre>
	$('#SearchFAQ').click(function () {
		$('#FAQ').children('div.TopicContents').hide().children('div.Answer').hide();
		if ($('#FAQSearch').val() != '') {
			try {
				$('.highlight').removeClass("highlight");
				$('div.Answer:Contains(' + $('#FAQSearch').val().toUpperCase() + ')').each(function () {
					$(this).show().parent().show(300);
					$(this).html(
						$(this).html().replace(
							new RegExp($('#FAQSearch').val(), "ig"), 
							function(match) {
								return '<span class="highlight">' + match + '</span>';
							}
						)
					)
				});
			}
			catch (err) {
				alert(err);
			}
		}
		return false;
	});
</pre>

<p>
	This is the entire searching function. Lets go through it line by line:
	<ul>
		<li>When the user clicks the search button
			<ul>
				<li>Collapse all Topics and Questions</li>
				<li>If the user actually typed something into the search box
					<ul>
						<li>Try the next block of code (so we can handle any errors gracefully)
							<ul>
								<li>Remove all highlighted terms from previous searches</li>
								<li>Using the previously mentioned selector, look for the term typed in the search box in all divs with class <em>Answer</em> and execute the following code
									<ul>
										<li>Set the answer and parent topic to visible</li>
										<li>change the Answer's HTML contents to: 
											<ul>
												<li>Perform a search and replace on the Answer's HTML
													<ul>
														<li>Create a new case insenstive regular expression with the search term</li>
														<li>Perform the following when it finds the search term
															<ul>
																<li>Wrap the search term in a span with class <em>highlight</em></li>
															</ul>
														</li>
													</ul>
												</li>
											</ul>
										</li>
									</ul>
								</li>
							</ul>
						</li>
						<li>Catch any errors
							<ul>
								<li>Show an alert with the error - for debugging purposes</li>
							</ul>
						</li>
					</ul>
				</li>
				<li>return false so that it does not cause a page post-back/form submission.</li>
			</ul>
		</li>
	</ul>
</p>


<p>
	In my css file I just set .highlight to 'background-color: yellow;' as a quick demo.	
</p>

<p>
	Hope you enjoyed this and learned something from it. It was quite an adventure getting 
	all of the pieces to line up properly. The hardest parts to get working, mostly due to 
	my lack of experience with JavaScript/jQuery, was the Regular Expression setup and the 
	proper order to perform the HTML substitution. Even when it ran without errors it would 
	not change any of the text. May my battle be your benefit.
</p>
<br />
<br />
<a href="http://RTHilton.com/Blog/Demos/FAQDemo.html" target="_blank" style="border: 1px solid rgb(50,50,50); background-color: rgb(90,90,90); color: rgb(255,255,255); padding: 10px;">View the demo</a>
<br />
<br />


]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=31]]></link>
         <pubDate>Wed, 02 Mar 2011 01:02:11 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[HTC Droid Incredible 2.2 Bug Killing Exchange Services]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p> It looks like there is a bug in the 2.2 firmware for the HTC Droid Incredible that will fill up your exchange logs and add overhead to your HTTP logs. </p>  <p> We saw our HTTP logs increase from ~20MB/day to over 100MB/day. On our message store logs we saw an increase from 1-2 log files, 5MB each, every minute to over 20. </p>  <p> Shutting off the phones saw an immediate reduction in the logfile creation. </p>  <p> An example of one of our users showed a drastic increase in HTTP log sessions, as follows: </p> <ul>  <li>9/07 – 803 entries</li> <li>9/08 – 2768 entries</li> <li>9/09 – 408414 entries</li> <li>9/10 – 428765 entries</li> </ul> <p> Both users are using the same phone and are on Verizon. </p>  <p> One user is also experiencing a calendar issue where multiple calendar items are appearing even after deletion and pages of reminders from the past keep coming up. </p>  <p> There are lots of forum entries around the web on this, but no fixes that I've been able to locate. Hopefully HTC/Verizon will have a fix out soon. Until then we are waiting for the users to bring their phones in so we can perform some diagnostics and testing on them. </p>  <p> If you know of any good fixes or are having this issue, drop a line in the comments. The best solution we have for now is to disable OMA or Mobile Network on the device and wait for a fix or install a 3rd party e-mail application. </p>]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=29]]></link>
         <pubDate>Mon, 20 Sep 2010 10:59:31 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[My Recent SAN Migration]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<h1>SAN Migration Summary</h1> <h2>Environment 1:</h2> <ul> <li>1 XIOTech Magnitude 3D 3000 (Old)</li> <li>Cisco 9100 SAN Fabric (Old)</li> <li>2 XIOTech Emprise 5000 (New)</li> <li>Cisco 9124 SAN Fabric (New)</li> <li>12 TB of Data</li> <li>34 SAN Volumes</li> <li>2 SAN Connected Physical Windows Servers (MPIO) <ul><li>File Servers running VSS and DFS/DFS-R</li></ul></li> <li>1 Windows Server Added to New SAN (MPIO) <ul> <li>Exchange Server 2003</li> </ul> </li> <li>3 vSphere 4.0.0-267974 Enterprise Nodes <ul> <li>24 Virtual Servers <ul> <li>2 SQL Servers <ul> <li>1 SQL Server has 3 RDM Volumes</li> </ul> </li> <li>2 Web Servers</li> <li>1 Domain Controller</li> <li>1 Print Server</li> <li>18 App/Test Servers</li> </ul> </li> <li>5 SAN Connected VMFS Volumes (MPIO)</li> <li>3 SAN Connected RDM Volumes (MPIO)</li> </ul> </li> </ul> <h2>Environment 2:</h2> <ul> <li>1 XIOTech Magnitude 3D 3000 (Old)</li> <li>1 XIOTech Emprise 5000 (New)</li> <li>Cisco 9124 SAN Fabric (New)</li> <li>6.6 TB of Data</li> <li>23 SAN Volumes</li> <li>3 SAN Connected Physical Windows Servers (MPIO) <ul> <li>2 File Servers running VSS and DFS/DFS-R</li> <li>1 SQL 2003 Server</li> </ul> </li> <li>3 Independent ESXi 4.0.0-171294 Nodes <ul> <li>14 Virtual Servers <ul> <li>1 SQL Server with 1 RDM Volumes</li> <li>2 Web Servers <ul> <li>1 Web Server has 2 RDM Volumes</li> </ul> </li> <li>1 SCCM Server with 1 RDM Volumes</li> <li>1 File Server with 1 RDM Volumes</li> <li>9 App/Test Servers</li> </ul> </li><li>3 SAN Connected VMFS Volumes (MPIO) <ul> <li>1 Assigned to 1 Node</li> <li>2 Shared by 2 Nodes</li> </ul> </li> <li>4 SAN Connected RDM Volumes (MPIO)</li> </ul> </li> </ul> <h2>Environment 1 Process</h2> <p> The first environment had a layer of complexity over the second because I had two independent fabrics. Luckily all servers were configured with MPIO support so I was able to remove one fibre pair, at the cost of performance, add place the servers into both zones. </p> <h2>Preparation</h2> <p> To prepare for the move I created all volumes ahead of time with a slight buffer (1GB) to handle mirroring of volumes. Both SANs and SAN Fabrics remained online during the entire process. </p> <h3>ESX Nodes</h3> <ol type="1"> <li>Moved 1 fibre channel port to the new fabric (Switch 1) from each node.</li> <li>Zoned the fabric to allow the hosts to communicate with both SANs. <ol type="a"> <li>With the Emprise 5000 Port 1 only needed to see MRC1 on both controllers, since they are plugged in to Switch 1, resulting in creating of 2 zones per server. Zone 1 contained Server1-HBA1/ISE1-MRC1 and Server1-HBA1/ISE2-MRC1.</li> </ol> </li> <li>Created host record on each Emprise 5000 for each ESX node (ESX01/ESX02/ESX03).</li> <li>Assigned all new volumes to all ESX nodes so that they were seeing the same thing.</li> <li>Rescanned the HBA in the vSphere Infrastructure Client and verified that all volumes were seen.</li> <li>All datastore volumes were configured as VMFS volumes; RDP volumes were untouched at this point.</li> <li>Using Storage vMotion I was able to migrate all virtual servers live from the old datastore to the new datastore. This process completed rather quickly, moving all VMs within about an hour.</li> <li>With the RDMs I assigned them to their appropriate servers and then followed the process below for Windows Servers starting at step 7.</li> </ol> <p> vSphere’s Storage vMotion proved to be a wonderful tool and worked without issue. There was zero downtime for any virtual server during the entire migration, aside from  </p> <h3>Existing Windows Servers</h3> <ol type="1"> <li>Verified that both fibre channel links were active.</li> <li>Moved 1 fibre channel port to the new fabric (Switch 2) from each server.</li> <li>Verified that my redundant paths were no longer fault tolerant.</li> <li>Zoned the fabric to allow hosts to communicate with both SANs. <ol type="a"> <li>This time I zoned for MRC2 on each Emprise 5000, since I am connected to Switch 2, which is where MRC2 is connected for each Emprise.</li> </ol> </li> <li>Created host records on each Emprise 5000 for each server.</li> <li>Assigned all new volumes to their appropriate server and using Disk Management mmc rescanned and verified that I had all volumes.</li> <li>I then initialized and converted all new volumes to dynamic disks.</li> <li>I didn’t want DFS-R to have issues with dismounting volumes so I stopped the ‘DFS Replication’ service at this point.</li> <li>If the server was a SQL server or other App server I would also stop the appropriate services to prevent I/O during the conversion process. </li><li>Since VSS for all volumes is located on a single, dedicated volume (V:\) I decided to convert this one to a dynamic disk first. To do this you must dismount all volumes that are using VSS and this as the storage location using ‘mountvol <drive letter="">:\ /P’. <strong>This is critical process. Per the documentation I have found if VSS loses communication for more than 20 minutes you will lose all of your snapshots. Bird-dogging this process and following these steps worked very well, deviating from these steps resulted in one server losing all VSS snapshots. VSS snapshots should never be used for a primary backup.</strong></drive></li> <li>Once all drives were dismount I then converted the disk to dynamic.</li> <li>After the VSS volume successfully completed I immediately remounted all volumes back to their original drive letters.</li> <li>I then selected all remaining, existing volumes and converted them to Dynamic Disks.</li> <li>Now that all of my new and old drives are dynamic I went from largest to smallest and added the new drive as a mirror to the old drive. I did it this way because the mirroring will only show you drives large enough to be a mirror, starting from largest to smallest I would only get one drive as an option each time as I moved down the list.</li> <li>I was now able to enable the ‘DFS Replication’ service without worry.</li> <li>After mirroring was completed I right clicked on each original volume in the ‘Disk Management’ mmc and removed it from the mirror. This will leave a Simple volume with only the new volumes being used.</li> </ol> <h3>Exchange Server Move</h3> <p> The Exchange server was a much smoother and more straight forward process, since it was not previously SAN connected. </p> <ol type="1"> <li>Zoned the fabric to allow host to communicate with the new SANs. <ol type="a"> <li>Since I have 2 HBAs going to 2 Emprise 5000s I needed to create 4 zones for this host:  <ol type="i"> <li>Host-HBA1/ISE1-MRC1</li> <li>Host-HBA1/ISE2-MRC1</li> <li>Host-HBA2/ISE1-MRC2</li> <li>Host-HBA2/ISE2-MRC2</li> </ol> </li> </ol> </li> <li>Created host records on each Emprise 5000 for the server, including both HBAs.</li> <li>Assigned all new volumes to their appropriate server and using Disk Management mmc rescanned and verified that I had all volumes. For each storage group I created a RAID 5 volume for the database files and a RAID 10 volume for the transaction logs.</li> <li>Initialized and created partitions on all of the new volumes</li> <li>Using Exchange System Management I then went to each Storage Group and changed the paths of the log files and system directories to point to the new Log volume. Exchange will then dismount the store and move those items to the new location, remounting the store when completed.</li> <li>In each Mailbox Store for each storage group I then went through and changed the path for the database and streaming database and pointed them at their new location. Again, Exchange will dismount the store, move the files for you and remount the store when completed.</li> </ol> <p> Exchange’s tools made the process extremely easy, though slow, and it was a downtime event for all users of the mailbox store during the process. </p> <h3>Final Steps</h3> <p> After all servers were done mirroring their volumes and the mirrors were broken I was able to use ICON Manager, a XIOTech tool, to verify that there was no more I/O to any of the volumes, if there were I would then be able to go to the assigned server and see what it was still using on that volume. </p> <p> After I/O was verified I then removed the zoning to the original SAN for all servers and moved their secondary HBA to the new fabric. The fabric was then appropriately zoned and using the MPIO tools verified that I now had 2 paths to each volume. </p> <p> In the vSphere Infrastructure Client I then went in and ensured that all volumes were set to Round Robin in their path settings to ensure the load was spread across all HBAs. </p> <p> The only downtime during this process was during the conversion to dynamic disks where it requires the volumes be dismounted during the process and the Exchange migrations. We did have 1 issue during the mirroring where several customers received delayed write failures to the DFS path of their H: drive, where they had rather large PST files located, though no data loss appears to have occurred. Aside from that the migration was successful. </p> <p> The mirroring was started around 2am on a Wednesday and completed by 9:30am Thursday morning. Total migration process took around 39 hours from start to finish. </p> <p> 1 site down, 1 to go. Only 10 user issues out of 700 and those were due to extremely large (2+ GB) PST files choking with the limited disk I/O and degraded server performance during the migration. </p> <h3>Environment 2 Process</h3> <p> Environment 2 was very similar to Environment 1 with the following differences, which is the only thing that I will cover here: </p> <ul> <li>Servers were already setup MPIO and the only change was zoning them for the new fabric, with 2 zones per server as follows: <ul> <li>ServerX-HBA1/ISE1-MRC1</li> <li>ServerX-HBA2/ISE1-MRC2</li> </ul> </li> <li>ESX nodes did not support Storage vMotion, manual migration of VMs was required and was a downtime event.</li> </ul> <h3>ESXi Migration Process</h3> <p> For the ESXi migration I followed the same zoning procedures as outlined previously. The ESXi servers had SSH enabled for remote console access from previous work. You can also use the vCenter Converter, but I found it took longer. </p> <ol type="1"> <li>All volumes were mapped from the new SAN to the servers</li> <li>HBAs were rescanned and verified that all volumes were visible</li> <li>Configured MPIO on all volumes and set the mode to Round Robin</li> <li>Formatted all data stores as VMFS</li> <li>Shutdown all Virtual Machines on a given server</li> <li>In the Edit Settings dialog I made note of all RDMs assigned to the server and removed them, deleting files from the disk. <strong>This is a critical step, failure to do this will result in the RDMs being copied in their entirety to the new datastore, not just the reference to the data store.</strong></li> <li>From the remote management console, as root, went through each data virtual server folder on each datastore and verified that no RDMs remained. I then copied the virtual server’s folder, in full, to the new datastore.</li> <li>After copying completed I went into the vSphere Infrastructure Client and browsed the new datastores. Entering in each virtual server folder and right clicking the .vmx file and choosing ‘Add to Inventory’ I then gave it a name such as ‘ServerName-E5K’ to denote that it was now located on the Emprise 5000.</li> <li>If the server did not have any RDMs this process is done and you can now power on the new VM, choose that it was Moved when prompted and remove the original from the inventory.</li> <li>If the server did have RDMs you will now need to add the original RDMs back in as well as the new RDMs. You can then start the virtual server and perform data migration from old to new as mentioned above in the section ‘Existing Windows Servers’ starting at step 7.</li> </ol> <h3>Final Steps</h3> <p> After I/O was again verified, all zoning to the original SAN was removed. No fibre channel cables needed patched due to everything being connected to the same fabric. </p> <h3>Summary</h3> <p> Because of the ESXi migrations Environment 2 ended up taking 2 full nights of hands-on work. All data was fully migrated within a span of 4 days, only because I waited a day in between the night shifts. </p> <p> We had 1 issue with our Blackberry BES server which was related to it losing communication with Exchange, which a reboot of BES fixed. Aside from that the only issue reported was from an off-site user who was not notified and could not communicate with an app server for a few hours, which was virtual and being migrated. </p>]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=28]]></link>
         <pubDate>Tue, 07 Sep 2010 14:55:48 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Creating a Basic jQuery Slideshow]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p> I needed a basic slideshow/image rotator with just the features I wanted and nothing more, and I needed a good excuse to play with jQuery. Here's the result of satisfying both of those needs. Look forward to another article that will expand upon this basic slideshow with pausing, text and navigation controls. Until then, enjoy. </p> 

<p>
	To get started we need to create a basic html file, we’ll call it SlideShow.html Here’s how it should look.
</p>

<pre>
&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot;
   &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;title&gt;jQuery Slideshow Demo&lt;/title&gt;
&lt;meta name=&quot;Author&quot; content=&quot;Ryan T. Hilton&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>

<p>
	Add jQuery from Google by placing this in your &lt;head&gt; section
</p>

<pre>
	&lt;script type=&quot;text/javascript&quot; src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js&quot;&gt;&lt;/script&gt;
</pre>

<p>
	We also need to add some styles to the page. Create a file called style.css and add this to the &lt;head&gt; section.
</p>
<pre>
	&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;style.css&quot; /&gt;
</pre>
<p>
	And now we’ll add some basic styles to style.css
</p>
<pre>
	body
	{
		background-color: rgb(45,45,45);
	}
	.Container
	{
		width: 500px;
		margin: 20px auto;
		border: 4px solid rgb(255,255,255);
		background-color: rgb(200,200,200);
		color: rgb(45,45,45);
		position: relative;
		height: 309px;
	}
	#slideshow
	{
		margin: 0;
		padding: 0;
		list-style: none;
		position: relative;
	}
	#slideshow li
	{
		float: left;
		position: absolute;
	}
</pre>

<p>
	Now, let’s setup the HTML for the slideshow. Add this between the &lt;body&gt; and &lt;/body&gt; tags. Just add an extra &lt;li&gt; for each image.
</p>

<pre>
	&lt;div class=&quot;Container&quot;&gt;
		&lt;ul id=&quot;slideshow&quot;&gt;
			&lt;li&gt;
				&lt;img src=&quot;Images/Image1.jpg&quot; width=&quot;500&quot; height=&quot;309&quot; /&gt;
			&lt;/li&gt;
			&lt;li&gt;
				&lt;img src=&quot;Images/Image2.jpg&quot; width=&quot;500&quot; height=&quot;309&quot; /&gt;
			&lt;/li&gt;
			&lt;li&gt;
				&lt;img src=&quot;Images/Image3.jpg&quot; width=&quot;500&quot; height=&quot;309&quot; /&gt;
			&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/div&gt;
</pre>

<p>
	Let’s create a new .js file and add it to the &lt;head&gt; section now as well. We’ll call it SlideShow.js and place it in the same directory as the html file
</p>
<pre>
	&lt;script type=&quot;text/javascript&quot; src=&quot;SlideShow.js&quot;&gt;&lt;/script&gt;
</pre>

<p>
	Now, we can start with the basics of the JavaScript. The rest of this will be done in SlideShow.js<br />
	First lets set up some variables to configure the behavior.
</p>
<pre>
	var fadeTime = 1200; // Fade in/out each image over 1.2 seconds
	var pauseTime = 4000; // Pause on each image for 4 seconds
</pre>
<p>
	Now we need to setup a few internal variables that we’ll use to maintain the state of the slideshow.
</p>
<pre>
	var current = 0; // Current index
	var cur; // Stores current item
	var next; // Stores next item
	var timer; // Used for starting/stopping the rotation
	var size = 0; // Stores the number of items to rotate
	var paused = false; // Are we paused?
</pre>
<p>
	How about we get started making things work? We’ll start by creating an initialization function. This will get everything setup and start the rotation.
</p>
<pre>
	function StartSlideShow()
	{
		// Hide all of the items
		$('ul#slideshow &gt; *').css(&quot;display&quot;, &quot;none&quot;)
		.css(&quot;left&quot;, &quot;0&quot;)
		.css(&quot;top&quot;, &quot;0&quot;)
		.css(&quot;position&quot;, &quot;absolute&quot;);

		// Display the first item
		$('ul#slideshow li:first').css(&quot;display&quot;, &quot;block&quot;)
		.css(&quot;left&quot;, &quot;0&quot;)
		.css(&quot;top&quot;, &quot;0&quot;)
		.css(&quot;position&quot;, &quot;absolute&quot;);

		// Set size variable to number of items
		size = $('ul#slideshow li').size();
		
		// Start the rotation timer
		StartTimer();
	}

	function StartTimer()
	{
		// Make sure we have a clean slate
		clearInterval(interval);
		// Call Switch() every x milliseconds
		interval = setInterval(‘Switch()', pauseTime);
	}

	function Switch()
	{	
		cur = ($(‘ul#slideshow li’).eq(current));
		// Check to see if we are at the end of the list
		if ((current + 1) == size)
		{
			next = ($('ul#slideshow li').eq(0));
			current = 0;
		}
		else
		{
			next = ($('ul#slideshow li').eq(current + 1));
			current = current + 1;
		}
		// Fade between the images
		cur.fadeOut(fadeTime);
		next.fadeIn(fadeTime);
	}
</pre>
<p>
	Ok, we are all done with SlideShow.js for now. Let’s go back to our SlideShow.html file. In the head section, add the following. This will tell jQuery to run StartSlideShow() when the page is loaded.
</p>
<pre>
&lt;script type=”text/javascript”&gt;
&lt;!--
	$(document).ready(function() {
		StartSlideShow();
	});
--&gt;
&lt;/script&gt;
</pre>

<p>
	You should now be able to run your slideshow by opening the HTML file in your favorite browser.
</p>
<p>
	<a href="http://rthilton.com/Projects/jQuery%20Slideshow%20v1/SlideShow.html">View Demo</a><br />
	<a href="http://rthilton.com/Projects/jQuery%20Slideshow%20v1.zip">Download</a>
</p>
]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=27]]></link>
         <pubDate>Thu, 01 Apr 2010 13:39:51 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[MAS90 ODBC Tool]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p>I've been expanding my support offerings to an old client as of late into the realm of Sage Software's MAS90. It started off with a redesign of their website and the addition of customer data needing to be uploaded regularly to the web site's database from their internal MAS90 installation.</p> <p>They are currently run MAS90 using the ProviderX ODBC connections. I haven't worked out all of the nomenclature and inner workings yet, so forgive me if I misspeak here.</p> <p>Not knowing anything about MAS90 when I started I had to leap over a few hurdles to get .Net applications to talk to the database, run queries and tell me the schema of the tables. There may be an easier way of doing this but I was unable to find one, so I broke out some .Net ODBC tutorials, what a mess.</p> <p>After constantly rewriting and recompiling my test applications to test the different queries I finally got sick of it and wrote a command line interface (CLI) for interacting with the MAS90 database.</p> <p>Once configured it allows you to throw ad-hoc queries at the database and, if you choose, see the schema. It is a very rough cut but I hope that it helps somebody else out. If not, it has done and will continue to do what I need it to do.</p> <p>You can download the application <a href="http://RTHilton.com/Projects/MAS90 CLI v1.0.zip">here</a></p> <p>From the readme:</p> <p>This is the initial release of my MAS90 CLI. This was developed for testing queries against MAS90 for a customer so that I could learn the internal structure of the MAS90 database tables and extract  sample data from them.</p> <p> To use:<br /> &nbsp;&nbsp;&nbsp;Open MAS90CLI.exe.config and setup your connection string, such as server name and path to your ProviderX libraries </p> <p>You can also set default values in the configuration file</p> <p>run MAS90CLI.exe it should connect successfully and put you at a MAS90&gt; prompt. </p><p>Setting Variables:<br /> &nbsp;&nbsp;&nbsp;Syntax: <br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<em>set [variable] = [value]</em><br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<em>set showschema = true</em><br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<em>set recordlimit = 30</em><br /> &nbsp;&nbsp;&nbsp;Variables:<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timeout - timeout in seconds for the query to run (0+)<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;recordlimit - number of records to return before quitting (1+, 0 = all)<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;showschema - prints out the schema for debugging purposes (true/false) </p> <p>Viewing Variables:<br /> &nbsp;&nbsp;&nbsp;Syntax: <br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<em>get [variable]</em><br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<em>get showschema</em><br /> &nbsp;&nbsp;&nbsp;Variables:<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timeout<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;recordlimit<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;showschema </p> <p>Running Queries: At the MAS90&gt; prompt type in your SQL query. You may need to reconfigure your console window to fit the data</p> <p><em>MAS90&gt; SELECT * FROM CUS_Invoices ORDER BY InvoiceDate DESC</em></p> <p>Queries use the standard SQL syntax as allowable via the ProviderX ODBC provider.</p> <p>You should start seeing the data appear followed by the number of records found and the time the query took to run</p> <p>Quitting:<br /> &nbsp;&nbsp;&nbsp;From the MAS90&gt; prompt type in 'quit' or 'exit' </p> <p> If you have any questions or problems feel free to contact me: </p> <p> Ryan T. Hilton<br /> Pacific NW Data Consultants<br /> <a href="mailto:Ryan@pnwdc.com">Ryan@pnwdc.com</a><br /> <a href="http://PNWDC.com">http://PNWDC.com</a> - <a href="http://RTHilton.com">http://RTHilton.com</a> </p>]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=25]]></link>
         <pubDate>Fri, 26 Feb 2010 13:10:26 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Virus and Malware Cleanup and Protection]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[I have been asked many times recently to clean up computers that have been infected with various Trojans, Virii, Worms, etc. They all end about the same way, me making a house call.<br /> <br /> Now, while I am usually willing to help people sometimes it just makes sense for people to help themselves some. Hopefully this will help you.<br /> <br /> <b><font size="3">Recommended Software</font></b><br /> I recommend <a href="http://free.avg.com/">AVG&nbsp;</a>for day to day anti-virus software. They seem to stay up to date with signatures, it is free for home use and usually takes care of most of the threats I've come across.<br /> <br /> For cleanup I also recommend <a href="http://www.malwarebytes.org/">MalwareByte's Anti-Malware</a>. I have found it to clean up several trojans that nothing else seems to touch<br /> <br /> <b><font size="3">Cleanup Instructions</font></b><br /> One common theme I have found with removal of malware is that it needs to be performed in a special bootup mode called <i>Safe Mode</i>. <i>Safe Mode</i> allows Windows to load a minimal amount of resources and limit what starts up automatically, including most pieces of malware and their self-defense mechanisms. To boot into <i>Safe Mode</i> follow these steps:<br /> <ol> <li>Shutdown Your Computer</li> <li>Press the power button on your computer</li> <li>You should now see the BIOS loading, this usually has your computer manufacturers logo, hardware information or both.</li> <li>Press F8 repeatedly until you are prompted for how you would like to start Windows - If you see the Windows loading screen then you will need to try again.</li> <li>From the list select 'Safe Mode with Networking'</li> <li>You should now see many lines of text scroll across your screen rapidly followed by Windows starting up and telling you that you are in <i>Safe Mode</i></li> </ol> <br /> From <i>Safe Mode</i>, download and install both products. These should be installed immediately after you receive a new computer but since you are reading this article chances are that wasn't done. Not to worry, we will do this now.<br /> <br /> After you have installed both products first run AVG, have it update the virus definitions from the internet and then run a full scan. Any items discovered should be selected and deleted. <br /> <br /> Now, repeat the above step for MalwareByte's Anti-Malware. This product is key for removal of some items such as 'AntiVirus 2009' and 'VirusShield 2009'. To date this is the only program that I have found to affectively remove these programs. <br /> <br /> Your computer should hopefully be clean now and running much faster. For some added benefit you should also defragment your C: drive. <br /> <br /> If you have any other ideas, feel free to share them in the comments.]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=24]]></link>
         <pubDate>Tue, 22 Sep 2009 11:54:13 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Custom Thread Manager In C#]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p> I was recently working on a project that required me to limit the number of background threads that could be executing at any given time. It could be my lack of knowledge of how the built in .Net classes work for managing thread execution, but I decided to embark on a quest to work up my own solution to this problem. The below code was hastily written to solve the problem at hand, but you should be able to see the underlying concept I used. Bare with me. </p> <p> First, I needed some thread parameters so that I could pass these to my scanner. Here is the class that I created to handle this. Name is the name of the Domain Controller we are going to query. </p>

<pre class="csharpcode">
<span class="kwrd">public</span> <span class="kwrd">class</span> ThreadParams
{
    <span class="kwrd">public</span> <span class="kwrd">string</span> BaseOU = <span class="str">""</span>;
    <span class="kwrd">public</span> <span class="kwrd">string</span> Name = <span class="str">""</span>;
    <span class="kwrd">public</span> ThreadParams(<span class="kwrd">string</span> baseOU, <span class="kwrd">string</span> name)
    {
        BaseOU = baseOU;
        Name = name;
    }
}    
</pre>

 <p> Next I created a class that acted as the shell of my scanner, called LastLogonScanner. </p>

<pre class="csharpcode">
<span class="kwrd">public</span> <span class="kwrd">class</span> LastLogonScanner
{
    <span class="kwrd">private</span> System.DirectoryServices.ActiveDirectory.DomainControllerCollection _domainControllers;
    <span class="kwrd">public</span> System.DirectoryServices.ActiveDirectory.DomainControllerCollection DomainControllers
    {
        get
        {
            <span class="kwrd">return</span> _domainControllers;
        }
        set
        {
            _domainControllers = <span class="kwrd">value</span>;
        }
    }
    <span class="kwrd">private</span> System.DirectoryServices.ActiveDirectory.Domain _domain;
    <span class="kwrd">public</span> System.DirectoryServices.ActiveDirectory.Domain Domain
    {
        get
        {
            <span class="kwrd">return</span> _domain;
        }
        set
        {
            _domain = <span class="kwrd">value</span>;
        }
    }
    <span class="kwrd">public</span> LastLogonScanner()
    {
        _domain = GetDomain();
        EventLogger(String.Format(<span class="str">"Enumerating DCs for domain {0}"</span>, _domain.Name));
        _domainControllers = GetDCList(_domain);
        EventLogger(String.Format(<span class="str">"Found {0} DCs"</span>, _domainControllers.Count.ToString()));
    }
    <span class="kwrd">public</span> System.DirectoryServices.ActiveDirectory.Domain GetDomain()
    {
        <span class="kwrd">return</span> System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
    }
    <span class="kwrd">public</span> System.DirectoryServices.ActiveDirectory.DomainControllerCollection GetDCList(System.DirectoryServices.ActiveDirectory.Domain domain)
    {
        <span class="kwrd">return</span> domain.DomainControllers;
    }
}
</pre>


<p> Now, here is where the real meat of it lies. For each DC it will create a thread. Then it will monitor each thread to see if it has started, if it has not been started and there are less than 5 running threads, start up a new thread and pass the base OU and the Domain Controller you wish to scan to it.</p> <br /> 

<pre class="csharpcode">
<span class="kwrd">public</span> <span class="kwrd">void</span> GetLastLogonTimes(<span class="kwrd">string</span> baseOU)
{
    List&lt;Thread&gt; threads = <span class="kwrd">new</span> List&lt;Thread&gt;();
    <span class="kwrd">foreach</span> (System.DirectoryServices.ActiveDirectory.DomainController dc <span class="kwrd">in</span> _domainControllers)
    {
        Thread t = <span class="kwrd">new</span> Thread(<span class="kwrd">new</span> ParameterizedThreadStart(GetUsersForDC));
        t.Name = dc.Name;
        threads.Add(t);
    }
    <span class="kwrd">while</span> (<span class="kwrd">true</span>)
    {
        <span class="kwrd">bool</span> threadsActive = <span class="kwrd">false</span>;
        <span class="kwrd">int</span> runningCount = 0;
        <span class="kwrd">int</span> pendingCount = 0;
        <span class="kwrd">foreach</span> (Thread t <span class="kwrd">in</span> threads)
        {
            <span class="kwrd">if</span> (t.IsAlive)
            {
                runningCount += 1;
            }
            <span class="kwrd">if</span> (t.ThreadState == ThreadState.Unstarted)
            {
                pendingCount += 1;
            }
        }
        <span class="kwrd">if</span> (runningCount &lt; 5)
        {
            <span class="kwrd">if</span> (pendingCount &gt; 0)
            {
                <span class="kwrd">foreach</span> (Thread t <span class="kwrd">in</span> threads)
                {
                    <span class="kwrd">if</span> (t.ThreadState == ThreadState.Unstarted)
                    {
                        ThreadParams tp = <span class="kwrd">new</span> ThreadParams(baseOU, t.Name);
                        t.Start((<span class="kwrd">object</span>)tp);
                        <span class="kwrd">break</span>;
                    }
                }
            }
        }
        <span class="kwrd">foreach</span> (Thread t <span class="kwrd">in</span> threads)
        {
            <span class="kwrd">if</span> (t.IsAlive)
            {
                threadsActive = <span class="kwrd">true</span>;
                <span class="kwrd">break</span>;
            }
        }
        <span class="kwrd">if</span> (!threadsActive)
        {
            <span class="kwrd">break</span>;
        }
    }
}
</pre>

<p>A bit of a kludge, I know, but it was very affective.</p> <p>You can download the full program <a href="http://www.rthilton.com/Projects/GetDomainLastLogon%20-%20v1.0.zip">here</a>. </p>]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=23]]></link>
         <pubDate>Fri, 19 Jun 2009 12:59:31 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Server 2003 Pre-R2 Domain-Wide Last Login Scanner ]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p>Looking for inactive accounts in your domain? The solution might be more difficult than you think, but I have the answer. I was recently tasked with providing a means to scan a domain of &nbsp;almost 100 domain controllers (DC) to see which accounts were 30, 60 and 90 days without a login. Because we were not yet at a full 2003 R2 domain functionality level we could not take advantage of the LastLogon attribute directly.</p> <p>For those who aren't aware, prior to Windows Server 2003 R2 the LastLogon schema attribute was not a replicated field. The solution that we came up with would be to connect to each DC in the domain directly, enumerate the list of users, retrieve their last logon time and then perform a comparison to find the most recent entry across all DCs. Here is a run down of the logic:</p> <ol> <li>Connect to local DC</li> <li>Enumerate list of all DCs in the domain</li> <li>Close connection</li> <li>For each DC discovered in step 2, create a connection to it</li> <li>Query all users and their LastLogon attribute</li> <li>Create array and store data from step 5 into it</li> <li>Close connection</li> <li>Compare arrays and find the most recent login for each user</li> <li>Output data to CSV file</li> <li>Manually analyze the data or write another program to do same</li> </ol> <p>Pretty basic, though there were some pitfalls that I encountered. One of them was I did not want to hammer the network, so I needed to limit my number of simultaneous connections. We decided to only connect to 5 DCs at a time.</p> <p>You can download the code and take it for a spin <a href="http://www.RTHilton.com/Projects/GetDomainLastLogon - v1.0.zip">here</a>.</p> ]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=21]]></link>
         <pubDate>Fri, 19 Jun 2009 12:13:49 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[DFS-R Fails To Replicate Some Files]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p>DFS-R fails to differentiate “long” filenames with filenames that appear as 8.3 short names. While this is rarely an issue, and even less so as our older files become obsolete, I have seen it happen in a couple situations. For example, take these two files:</p> &nbsp;&nbsp;&nbsp;Notification Procedures.doc<br /> &nbsp;&nbsp;&nbsp;Notifi~1.doc<br />   <p>To reproduce this fully, create a new folder on a replicated volume. Create a new document with the first filename and wait for it to replicate. Now, from the command prompt, type in ‘dir /x &lt;Source Directory&gt;’ and note the 8.3 filename is “NOTIFI~1.DOC”. Type in the same command but use the destination directory this time. You will note that it, too, has the same 8.3 filename. Next we need to create the second file. Create a new document and save it as “Notifi~1.doc”. You should not see this file replicate. If you now run the ‘dir /x’ command for the two directories you will see that the first document on the source has been changed to “NOTIFI~2.DOC” however on the destination this change has not been made. DFS will note that the file exists in both locations and no replication will occur.</p> <p>Again, this isn’t an issue unless you have a large collection of legacy filenames especially those that only maintained their short name.</p>   <p>One workaround that I have found, which will work after the fact, is to disable 8.3 filename creation on your NTFS volumes. You can do this on Server 2003, XP and Vista using the command ‘fsutil.exe behavior set disable8dot3 1’. For Windows NT and Windows 2000 you need to make a registry change. For more information on this change you can reference this <a href="http://support.microsoft.com/kb/121007">Microsoft article</a>.</p> ]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=20]]></link>
         <pubDate>Tue, 21 Oct 2008 13:14:44 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Server 2003 R2 DFS-R Replication Issues]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[I have been heavily investing in Domain DFS (Distributed File System) to help simplify our storage management. Microsoft's DFS really helps, providing the ability to store static files at each branch office and replicate changes to the local site using DFS-R (DFS Replication), that way the user will have quick access to them regardless of where they are. Another benefit of this combination is that you can create a warm failover by replicating the data throughout the day to your main site and then setting it as the primary DFS path if the branch server fails.<br /> <br /> While implementing this solution and setting up my replication I came across some pretty big issues, such as file counts between the source and destination being off by thousands (more on the source side). After visiting forum after forum I have finally learned that this was due to the files having the Temporary NTFS attribute set on them, which DFS-R will not schedule for replication. In looking at a large sampling of the files using&nbsp;<i>fsutil</i>&nbsp;(fsutil usn readdata &lt;Filename&gt;)they all contained this attribute as noted by the attribute being greater than 0x100 (The Temporary attribute). For example, 0x180, 0x120, etc. Most of these files were PDF files. I have not found any documentation yet regarding why the PDF files might have this attribute set, but I am going to keep looking.<br /><br /> To solve this problem I created a C# project that would scan the filesystem looking for these files and removing this attribute. A bit of a brute force approach, but there aren't very many legitimate uses for this in our environment. The source of the code is <a href="/Projects/File Attributes.zip">here</a>.<br /><br /> ]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=19]]></link>
         <pubDate>Mon, 20 Oct 2008 15:21:50 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[VMWare ESXi 3.5 Virtual Machine Migration]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[I recently had the joy of installing the latest free release from VMWare, ESXi 3.5. I must say it has been quite a pleasure so far. I am now up to my third installation of ESXi and can't wait to load up a few more hosts. Here are the two setups that I am currently running for ESXi:<br /><br /> Dell 2850<br /> 2 GB RAM<br /> Local RAID Array for VM Storage<br /> 1 Guest Operating System (Server 2003)<br /><br /> Dell 2850<br /> 4 GB RAM<br /> Local RAID Array for ISO Storage<br /> XioTech Magnitude 3D 3000 Fibre Channel SAN for VM Storage<br /> 9 Guest Operating Systems (Server 2003, Debian Etch)<br /><br /> Dell 2850<br /> 4 GB RAM<br /> Local RAID Array for ISO Storage<br /> XioTech Magnitude 3D 3000 Fibre Channel SAN for VM Storage<br /> 1 Guest Operating Systems (Server 2003)<br /><br /> The first system was loaded this way to add an additional Debian installation side by side with the Server 2003 installation and to improve some reliability issues we were seeing on the Server 2003 loaded bare metal.<br /><br /> The second and third system were originally Server 2003 R2 hosts with VMWare Server loaded hosting the 10 currently running Virtual Machines. These were running OK, but having to patch the host OS was a bit of a pain and we were pretty much at capacity. This is why we decided to move to ESXi. It would allow us a few more VMs with current hardware with the ability to add more RAM to the systems to scale them out further. Once we have a bit more money we will be investing in licensing for performing VMotion, but that is still a few months out, I will just be happy when I can get Virtual Center loaded and start performing Virtual Consolidate Backup (VCB) based backups.<br /><br /> Running pretty much all of the original VMs on a single ESXi host has proven to be almost a perfect fit. CPU utilization was very low, though memory was creeping up to about 3.5 GB used. One of our guests, running Microsoft SCCM, was complaining today about being low on Virtual Memory, so we're going to amp up the memory allocation to help it out so it isn't paging all that data.<br /><br /> Now, for what you came here for, moving the Virtual Machines. Since the last two hosts are both connected to the Fibre Channel XioTech SAN I configured a single volume for hosting the VMs on which they would share, thanks to VMFS-3. The first step is to shutdown the VMs that you want to move. After each VM has been shutdown right click on the VM in the Virtual Infrastructure Client (VI Client) and select &quot;Remove From Inventory.&quot; This will ensure that it won't be accidentally started back up on this host. Now, from your destination server select the Configuration tab in the VI Client and navigate to the Storage option. Find the Datastore that contains your VM files and right click and choose &quot;Browse Datastore.&quot; Now you must locate the VM Configuration file (*.vmx) for each VM that you wish to move. Right click this file and select &quot;Add To Inventory.&quot; You should now be able to right click each host and power them on. You will be prompted that the VM has been moved and asked to Create or Keep the identifier, if you do not see this and your VM does not start, click the Summary tab and the question will be there. I chose to keep the identifier as this machine was &quot;moved.&quot; Your VM should now completely start.<br /><br /> You will want to make sure that your network configuration is the same on both ESXi hosts or you may not have network connectivity when you start the VM from its new location.<br /><br /> Hope this helps, happy virtualizing.]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=15]]></link>
         <pubDate>Fri, 10 Oct 2008 18:38:21 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Warning: Windows customization resources were not found on this server]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[I just got VMWare ESX 3.5 loaded up and I went to create a new Virtual Machine from a template.&nbsp; I wanted to customize this VM, but the customization options were not available and it reported &quot;Warning: Windows customization resources were not found on this server&quot;.&nbsp; Googling around the web, and reading the VMWare documentation,&nbsp;everything said to extract SysPrep 1.1 into the “C:\Documents and Settings\All Users\Application Data\VMware\VMware VirtualCenter\sysprep\1.1” folder.&nbsp; I did this, no luck.&nbsp; I tried to extract the contents of the tools folder only into the root of this directory, no luck.&nbsp; I tried extracting them to the “\sysprep\xp” folder, no luck.<br /><br />After a bit of pondering and gluing some hair back onto my scalp I thought.&nbsp; Why not just grab them from the XP CD and see if that works.&nbsp; I also remembered that there were some significant changes to SysPrep for Windows XP.<br /><br />Here’s how I fixed it:<br />Copy the contents of “XP CD\SUPPORT\TOOLS\DEPLOY.CAB” into the folder “C:\Documents and Settings\All Users\Application Data\VMware\VMware VirtualCenter\sysprep\xp” on your Virtual Center server.&nbsp; Everything should be working now.<br /><br />Good luck!]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=13]]></link>
         <pubDate>Thu, 31 Jan 2008 10:57:52 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[An error occurred during configuration of the HA Agent on the host]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[I just received my new ESX servers at work, yay!&nbsp; I've been dying to play with some real VMWare technology.&nbsp; Don't get me wrong, VMWare Workstation and the free VMWare Server are some sweet wares, but nothing compares to 8 cores of bare-metal virtualization with 32 Gigs of RAM (per node).<br /><br />The first hurdle was licensing.&nbsp; I was unaware of how the licensing worked for ESX server and was not expecting the license activation process and the need to setup a Virtual Center server to handle the license serving.&nbsp; Not a big deal, I had a server I was about ready to retire anyways so I just reloaded it with Server 2003 R2 Standard and loaded up my Virtual Center with the licensing server.&nbsp; Problem solved, ESX servers licensed.<br /><br />The next hurdle was VMotion.&nbsp; I could not for the life of me figure out what needed to happen to setup VMotion.&nbsp; I kept getting errors saying <span style="font-style: italic">&quot;The VMotion interface is not configured (or is misconfigured) on the source host &quot;ESX01&quot;.</span>&nbsp; A little bit of googling and I discovered that I needed to setup a VMKernel interface <span style="font-weight: bold">and</span> enable it for VMotion.&nbsp; After playing around with that and setting up an internal 192.168.xxx.xxx network for VMotion I was rocking and rolling.&nbsp; Migrated a server in 2.25 minutes with 1 dropped ping, pretty frickin' sweet if you ask me.<br /><br />The final hurdle (for now) was setting up HA and DRS.&nbsp; Primarily the problem was with HA.&nbsp; I configured the cluster, added my hosts, but they all said <span style="font-style: italic">&quot;An error occurred during configuration of the HA Agent on the host&quot; </span>when trying to reconfigure HA.&nbsp; After even more googling I learned that DNS was the most common culprit.&nbsp; I verified DNS on all of the hosts, could ping them all by name and IP,&nbsp;short name&nbsp;and FQDN.&nbsp; What could possibly be the problem?&nbsp; Well, the problem was simple, and stupid, and in my opinion a bug and not a&nbsp;feature.<br /><br />ESX is appearantly case sensitive on <span style="font-weight: bold">hostnames</span>.&nbsp; So, my server labled in the hosts file as CORPESX01.DOM.LOC needed to be labeled as corpesx01.DOM.LOC in order to resolve properly.&nbsp; A simple <span style="font-style: italic">nslookup</span> from my Windows machine of one of the IP addresses confirmed this capitalization scheme. <span style="font-style: italic">&nbsp;</span>I went through each host file making them look as follows:<br /><br />10.10.10.1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;corpesx01.DOM.LOC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;corpesx01<br />10.10.10.2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;corpesx02.DOM.LOC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;corpesx02<br />10.10.10.3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;corpesx03.DOM.LOC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;corpesx03<br /><br />Life is now good.&nbsp; I disabled and reenabled HA on my cluster and voila!, everything reconfigured properly.<br /><br />Hope this helps!]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=12]]></link>
         <pubDate>Fri, 18 Jan 2008 16:36:42 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Custom ASP.NET DropDownList to Allow Multiple Text Fields]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[I had a need to create a DataBound DropDownList today with multiple items as the text field. The problem is that a DropDownList only supports one column for the text field.&nbsp; If you try to add more than that you will see the error: DataBinding: <span style="font-style: italic">'System.Data.DataRowView' does not contain a property with the name 'Column1, Column2'.</span><br /><br />I have seen this problem posted in many forums, and finally I myself got sick of searching for the solution.&nbsp; I found one, though the source was not free, so I set out to creat my own.&nbsp; I figured that it could be too hard.&nbsp; The trick was, I did not want to to do this with SQL, as I wanted it to be usable with my automatically generated stored procedures from my little O/R Mapper application.<br /><br />Pulling out reflector I started poking around in the System.Web.UI.DropDownList class.&nbsp; The method that I wanted to override is the PerformDataBinding method.&nbsp; I created a new class inheriting System.Web.UI.WebControls.DropDownList and created a new method overriding PerformDataBinding.&nbsp; I then opened that method up in reflector and copied most of the meat into my new method.<br /><br />I then removed everything from within the &quot;if (flag)&quot; block about halfway down, this is where the DataTextField is resolved, and replaced it with some new code.&nbsp; Here is what the new block looks like.

<pre class="csharpcode">
<span class="kwrd">if</span> (flag)
{
    <span class="kwrd">if</span> (dataTextField.Length &gt; 0)
    {
        <span class="kwrd">string</span>[] items = dataTextField.Split(<span class="str">','</span>);
        <span class="kwrd">object</span>[] resolvedItems = <span class="kwrd">new</span> <span class="kwrd">object</span>[items.Length];
        <span class="kwrd">for</span> (<span class="kwrd">int</span> i = 0; i &lt; items.Length; i++)
        {
            <span class="kwrd">string</span> s = items[i];
            s = s.TrimStart(<span class="str">' '</span>);
            s = s.TrimEnd(<span class="str">' '</span>);
            resolvedItems[i] = DataBinder.GetPropertyValue(obj2, s);
        }
        <span class="kwrd">if</span> (dataTextFormatString.Length &gt; 0)
        {
            item.Text = String.Format(dataTextFormatString, resolvedItems);
        }
        <span class="kwrd">else</span>
        {
            item.Text = String.Format(<span class="str">"{0}"</span>, resolvedItems);
        }
        <span class="rem">//item.Text = DataBinder.GetPropertyValue(obj2, dataTextField, dataTextFormatString);</span>
    }
    <span class="kwrd">if</span> (dataValueField.Length &gt; 0)
    {
        item.Value = DataBinder.GetPropertyValue(obj2, dataValueField, <span class="kwrd">null</span>);
    }
}
</pre>

As you can see from the code, I take the DataTextField property and split the contents, trimming any white space.&nbsp; I then resolve the databinding on them and&nbsp;place the results into an object[] list to use as a parameter for String.Format.&nbsp; If the format string is blank I default it to &quot;{0}&quot; to grab the first property.<br /><br />It isn't a glamorous solution, but it was quick, easy, and now the world can have it for free.<br /><br /><span style="font-weight: bold">Update:</span><br />For those of you looking for a more robust control to do this, that is done for you, <a href="http://www.eworldui.net">eXcentrics World</a> has <a href="http://www.eworldui.net/CustomControls/MultiText.aspx">binaries</a> available for free with a $75 charge for the source code.&nbsp; I downloaded this, though I haven't had a chance to install it and try it out.]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=6]]></link>
         <pubDate>Thu, 19 Jul 2007 06:51:12 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Adventures with a SQL Server Database Schema]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p>Being the main person in my department for developing database driven websites, I get tasked with creating all sorts of databases with web frontends.&nbsp; This task has lead to a fairly solid process that keeps evolving with time.&nbsp; The problem with this process is that there are too many manual steps, here is a breakdown of my workflow from a 1000 foot level:<ul><li>Create database structure with relationships</li><li>Create Visual Studio Solution and add a new website</li><li>Create a new DataSet</li><li>Drag all of my tables onto it</li><li>Create stored procedures for adds, updates, deletes and FetchBy's</li><li>Add pages with GridViews and FormViews for each table, or nested within a page where appropriate/necessary</li><li>Customize controls for various data types (Date Pickers, Drop Down Lists, etc.)</li><li>Impliment Security</li><li>Impliment Sitemap</li><li>Refine the interface</li></ul>As you can see, there are a number of steps here, and none of them are really generic enough to be reused from project to project, leading to many hours doing mundane things.<br /><br />Because of this I have started working on a custom Object-Relational Mapper (ORM) to create the DataSet for me from the database structure, generate the stored procedures, create the base web structure with SiteMap, MasterPages, GridViews, FormViews, etc.<br /><br />In doing this I have come across several little quirks.&nbsp; One of those deals with determining the maximum length, in bytes, of a column.&nbsp; Now, I know about COL_LENGTH and it worked great...on my local SQLExpress copy, however on our production server I could not get it to return anything other than NULL, so I set out to find a better solution.&nbsp; Here is the code that I was using to pull some of the SQL attributes about the columns:
</p>

<p class="code">
<span style="color: #0000ff">SELECT</span> TABLE_SCHEMA<span style="color: #808080">,</span> TABLE_NAME<span style="color: #808080">,</span> COLUMN_NAME<span style="color: #808080">,</span> <span style="color: #808080">(</span><br /><span style="color: #0000ff">SELECT</span> <span style="color: #ff00ff">COL_LENGTH</span><span style="color: #808080">(</span>TABLE_NAME<span style="color: #808080">,</span> COLUMN_NAME<span style="color: #808080">))</span> <span style="color: #0000ff">AS</span> <span style="color: #0000ff">Size</span><span style="color: #808080">,</span> <br />COLUMN_DEFAULT<span style="color: #808080">,</span> <br />IS_NULLABLE<span style="color: #808080">,</span> <br />DATA_TYPE<span style="color: #808080">,</span> <br />CHARACTER_MAXIMUM_LENGTH<span style="color: #808080">,</span> <br />NUMERIC_PRECISION<span style="color: #808080">,</span> <br />NUMERIC_SCALE <br /><span style="color: #0000ff">FROM</span> <span style="color: #008000">INFORMATION_SCHEMA.COLUMNS</span>
</p>

<p>
After discovering the NULL issue on our production server and not being able to resolve it easily I started probing around the sys.* tables and came up with the following solution:
</p>

<p class="code">
<span style="color: #0000ff">SELECT</span> TABLE_SCHEMA<span style="color: #808080">,</span> TABLE_NAME<span style="color: #808080">,</span> COLUMN_NAME<span style="color: #808080">,</span> <br /><span style="color: #808080">(</span><br /><span style="color: #0000ff">SELECT</span> max_length <span style="color: #0000ff">FROM</span> <span style="color: #008000">sys.columns</span> <span style="color: #0000ff">WHERE</span> <span style="color: #0000ff">name</span> <span style="color: #808080">=</span> COLUMN_NAME <span style="color: #808080">AND</span> <span style="color: #ff00ff">object_id</span> <span style="color: #808080">=</span> <br /><span style="color: #808080">(</span><br /><span style="color: #0000ff">SELECT</span> <span style="color: #ff00ff">object_id</span> <span style="color: #0000ff">FROM</span> <span style="color: #008000">sys.tables</span><br /><span style="color: #0000ff">WHERE</span> <span style="color: #0000ff">name</span> <span style="color: #808080">=</span> TABLE_NAME <span style="color: #808080">and</span> SCHEMA_ID <span style="color: #808080">=</span> <br /><span style="color: #808080">(</span><br /><span style="color: #0000ff">SELECT</span> schema_id <span style="color: #0000ff">FROM</span> <span style="color: #008000">sys.schemas</span> <span style="color: #0000ff">WHERE</span> <span style="color: #0000ff">name</span> <span style="color: #808080">=</span> TABLE_SCHEMA<br /><span style="color: #808080">)</span><br /><span style="color: #808080">)</span><br /><span style="color: #808080">)</span> <span style="color: #0000ff">AS</span> <span style="color: #0000ff">Size</span><span style="color: #808080">,</span> <br />COLUMN_DEFAULT<span style="color: #808080">,</span> <br />IS_NULLABLE<span style="color: #808080">,</span> <br />DATA_TYPE<span style="color: #808080">,</span> <br />CHARACTER_MAXIMUM_LENGTH<span style="color: #808080">,</span> <br />NUMERIC_PRECISION<span style="color: #808080">,</span> <br />NUMERIC_SCALE <br /><span style="color: #0000ff">FROM</span> <span style="color: #008000">INFORMATION_SCHEMA.COLUMNS</span>
</p>

<p>
We are getting closer, however now it is pulling view's as well which produce NULLs in the Size column, I just want tables for now.&nbsp; One more change and we should be set:
</p>

<p class="code">
<span style="color: #0000ff">SELECT</span> TABLE_SCHEMA<span style="color: #808080">,</span> TABLE_NAME<span style="color: #808080">,</span> COLUMN_NAME<span style="color: #808080">,</span> <br /><span style="color: #808080">(</span><br /><span style="color: #0000ff">SELECT</span> max_length <span style="color: #0000ff">FROM</span> <span style="color: #008000">sys.columns</span> <span style="color: #0000ff">WHERE</span> <span style="color: #0000ff">name</span> <span style="color: #808080">=</span> COLUMN_NAME <span style="color: #808080">AND</span> <span style="color: #ff00ff">object_id</span> <span style="color: #808080">=</span> <br /><span style="color: #808080">(</span><br /><span style="color: #0000ff">SELECT</span> <span style="color: #ff00ff">object_id</span> <span style="color: #0000ff">FROM</span> <span style="color: #008000">sys.tables</span><br /><span style="color: #0000ff">WHERE</span> <span style="color: #0000ff">name</span> <span style="color: #808080">=</span> TABLE_NAME <span style="color: #808080">and</span> SCHEMA_ID <span style="color: #808080">=</span> <br /><span style="color: #808080">(</span><br /><span style="color: #0000ff">SELECT</span> schema_id <span style="color: #0000ff">FROM</span> <span style="color: #008000">sys.schemas</span> <span style="color: #0000ff">WHERE</span> <span style="color: #0000ff">name</span> <span style="color: #808080">=</span> TABLE_SCHEMA<br /><span style="color: #808080">)</span><br /><span style="color: #808080">)</span><br /><span style="color: #808080">)</span> <span style="color: #0000ff">AS</span> <span style="color: #0000ff">Size</span><span style="color: #808080">,</span> <br />COLUMN_DEFAULT<span style="color: #808080">,</span> <br />IS_NULLABLE<span style="color: #808080">,</span> <br />DATA_TYPE<span style="color: #808080">,</span> <br />CHARACTER_MAXIMUM_LENGTH<span style="color: #808080">,</span> <br />NUMERIC_PRECISION<span style="color: #808080">,</span> <br />NUMERIC_SCALE <br /><span style="color: #0000ff">FROM</span> <span style="color: #008000">INFORMATION_SCHEMA.COLUMNS</span> <br /><span style="color: #0000ff">WHERE</span> TABLE_NAME <span style="color: #808080">in</span> <span style="color: #808080">(</span><span style="color: #0000ff">SELECT</span> <span style="color: #0000ff">name</span> <span style="color: #0000ff">FROM</span> <span style="color: #008000">sys.tables</span><span style="color: #808080">)</span>
</p>

<p>
Note the last line.&nbsp; This final query will give me most of the information I need to build out my columns.&nbsp; Hopefully you will find this useful.<br /><br />For those of you wondering about my ORM code, I have yet to release it however it is currently doing more than I set out to accomplish and it is all developed in C# with managed code with the exception of using SQLDMO to retrieve a list of servers.&nbsp; I will hopefully have something available for download soon, so sit tight.
</p>]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=5]]></link>
         <pubDate>Wed, 18 Jul 2007 16:42:11 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[Just a quick problem I ran across and a solution to it, so that I have a mental record of how I fixed this.<br /><br />Working with a DataSet in .Net I needed to add a new column to the table.&nbsp; The column was a Nullable VarChar(1000).&nbsp; Using a little shortcut to get the column added to the dataset I added&nbsp;the column to the GetData command text which will cause the DataSet Visual Designer to create the column reference.<br /><br />After battling with the wonderful&nbsp;&quot;Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.&quot; for a bout an hour I discovered that, while the SQL Column was set to Nullable, the DataSet disallowed Null values.&nbsp; While I should have been more careful and verified the visual designers work, it still would have been nice to have it pick this up automatically.&nbsp; That will teach me to use a shortcut.]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=4]]></link>
         <pubDate>Tue, 17 Jul 2007 14:40:35 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Customizing Links With The ASP.NET Calendar Control]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p>While working on a website for my son I wanted to add a calendar in the sidebar with links to dates that had photos or news articles on it.&nbsp; I managed to get everything customized except for the links; For some reason the styles just would not apply or it would add <em>style="Color:Black"</em> which was most definitely not what I wanted.&nbsp; As usual, I Googled away, however I did not find any results to solve this problem.&nbsp; Time to break apart the control and see how it works.</p>

<p>I was already subscribing to the Calendar's DayRender event to mark the day as being selectable, when looking at the cell's controls referenced by the DayRenderEventArgs, however, I noticed it was a LiteralControl which means very little customization. After sleeping on it I noticed that there was a property on the DayRenderEventArgs called SelectUrl.&nbsp; This property returns the javascript postback that is used to create the selectable link.</p>

<p>Now, armed with a reference to the table cell and the URL used for the link I decided to stop letting ASP.NET tell me what I wanted and instead told it.&nbsp; First I cleared the cell's controls, then I created a new HyperLink control, set its CssClass attribute, set its URL to e.SelectUrl, text to the current day and added it to the cell.&nbsp; Have a look at the code below.</p>

<pre class="csharpcode">
<span class="kwrd">protected</span> <span class="kwrd">void</span> Calendar1_DayRender(<span class="kwrd">object</span> sender, DayRenderEventArgs e)
{
      <span class="rem">// Do we have any dates in are array?</span>
      <span class="kwrd">if</span> (blogDates != <span class="kwrd">null</span>)
      {
            <span class="kwrd">foreach</span> (DateTime dt <span class="kwrd">in</span> blogDates)
            {
                  <span class="kwrd">if</span> (dt.ToShortDateString() == e.Day.Date.ToShortDateString())
                  {
                        <span class="rem">// We have a match, format the link</span>
                        e.Day.IsSelectable = <span class="kwrd">true</span>;
                        e.Cell.Controls.Clear();
                        HyperLink hl = <span class="kwrd">new</span> HyperLink();
                        hl.NavigateUrl = e.SelectUrl;
                        hl.CssClass = <span class="str">"CalLink"</span>;
                        hl.Text = e.Day.Date.Day.ToString();
                        e.Cell.Controls.Add(hl);
                  }
            }
      }
}</pre>
]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=1]]></link>
         <pubDate>Wed, 18 Apr 2007 06:26:03 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Item has already been added. Key in dictionary: ...]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[<p>Recently I had the need to create a GridView that would call a stored procedure with a single character as a parameter.&nbsp; This was used to implement custom alphabetized paging.&nbsp; The character value was derived from the QueryString after being sanitized and verified to be an appropriate value.&nbsp; Sounds pretty simple so far, right?</p><p>While setting up a GridView in ASP.NET 2.0 I came across the <span style="font-style: italic">Item has already been added. </span>error message when performing a sort.</p><p>The DataSource that I used was the ObjectDataSource.&nbsp; As with most of my projects, I create a Data Abstraction Layer, or DAL, to abstract all of my data access and give me more standardized methods to perform all of my database work.&nbsp; This process is well document on several websites, so I will not get into the details here.&nbsp; It is a DataSet created from within Visual Studio 2005, the back-end is SQL Express 2005 and consists of 2 tables, 8 total columns between them, a view and roughly 5 stored procedures per table to handle the common database functions: Get Records, Get Record By ID, Update Record, Add Record, Delete Record.</p><p>To start off I just set a default value on the ODS of &quot; &quot; to display&nbsp;everything.&nbsp;The GridView loaded the data from the DataSource using the default value, displayed it, and life was good.&nbsp; Just as I began to reach for a cookie for my efforts, I decided to sort it.&nbsp; That is when everything changed.&nbsp; I received the dreaded: <span style="font-style: italic">Item has already been added. Key in dictionary:&nbsp;</span> error message.</p><p>As usual, I Googled away for an hour and found no results to solve this problem.&nbsp; I began throwing in my Debug.WriteLine statements everywhere I could.&nbsp; GridView1_DataBinding, GridView1_DataBound, ObjectDataSource1_DataBinding.&nbsp; You name it, I wrote it.&nbsp; But nothing seemed to help.</p><p>I came across some articles referencing multiple DataBind calls to the GridView.&nbsp; I decided to look into my data binding some more.&nbsp; I decided that instead of using the DefaultValue on my SelectParameters I would set them manually using code behind.&nbsp; I tried setting it in Page_Load first, but noticed that it would not fire.&nbsp; I then decided to add it to Page_Init and voila, everything now works.&nbsp; Here is that code behind:</p>


<pre class="csharpcode">
<span class="kwrd">protected</span> <span class="kwrd">void</span> Page_Init(<span class="kwrd">object</span> sender, EventArgs e)
{
    <span class="kwrd">char</span> c = Char.ToUpper(Utilities.GetCharFromString(Request.QueryString[<span class="str">"Filter"</span>]));
    <span class="rem">// Check if we need to filter results</span>
    <span class="kwrd">if</span> (c != <span class="str">' '</span>)
    {
        <span class="kwrd">if</span> (Regex.Match(c.ToString(), <span class="str">"([0-9]|[A-Z])"</span>).Success)
        {
            <span class="rem">// Ok, we are valid, start the filtering</span>
            System.Diagnostics.Debug.WriteLine(<span class="str">"Character is a valid object: "</span> + c.ToString());
            ObjectDataSource1.SelectParameters[<span class="str">"Char"</span>] = <span class="kwrd">new</span> Parameter(<span class="str">"Char"</span>, TypeCode.String, c.ToString());
        }
        <span class="kwrd">else</span>
        {
            System.Diagnostics.Debug.WriteLine(<span class="str">"Character is not a valid object: "</span> + c.ToString());
            ObjectDataSource1.SelectParameters[<span class="str">"Char"</span>] = <span class="kwrd">new</span> Parameter(<span class="str">"Char"</span>, TypeCode.String, <span class="str">" "</span>);
        }
    }
    <span class="kwrd">else</span>
    {
        System.Diagnostics.Debug.WriteLine(<span class="str">"Character is null"</span>);
        ObjectDataSource1.SelectParameters[<span class="str">"Char"</span>] = <span class="kwrd">new</span> Parameter(<span class="str">"Char"</span>, TypeCode.String, <span class="str">" "</span>);
    }
}
</pre>


<p>If you would like further information, feel free to send me an <a href="mailto:rthilton@gmail.com?Subject=Blog%20Article:%20Item%20has%20already%20been%20added">e-mail</a></p><font size="4"><span style="font-weight: bold; font-style: italic">Update (<span style="color: #a9a9a9">Wedmesday March&nbsp;11, 2007 @&nbsp;8:50:07 PM</span><span style="color: #000000">):</span></span></font> <p>Well, now I feel like a complete and utter dope.&nbsp; The problem lied deeper than I originally thought.&nbsp; The reason that this worked is because it caused the ObjectDataSource to essentially be &quot;refreshed&quot;.&nbsp; Why did it need it?&nbsp; Well, that is because of a hack I did within my master page.</p><p>I had a couple of stylesheets in my MasterPage that were created using <span style="font-style: italic">&lt;% Page.ResolveUrl(&quot;~/Path/To/StyleSheet.css&quot;) %&gt;.</span> Later, I needed to add, from code behind, more stylesheets and javascript to the head section.&nbsp; That is when I came across the error: <a style="text-decoration: none" href="http://west-wind.com/WebLog/posts/5758.aspx"><span style="font-style: italic">The Controls collection cannot be modified because the control contains code blocks (i.e. &lt;% ... %&gt;).</span>&nbsp;</a>&nbsp;After doing quite a bit of research on this, I found a solution.&nbsp; From within my MasterPage's codebehind I would add:</p><font size="2"><p></p></font><font size="2"><p>Then I had to change&nbsp;my opening tag on the stylesheet references to&nbsp;use the&nbsp;databinding code blocks, <span style="font-style: italic">&lt;%# &nbsp;%&gt;</span>, I could then add anything I needed to the header by using <span style="font-style: italic">Page.Header.Controls.Add(Control);</span></p></font><font size="2"><p>This, unfortunately, had the side affect a week and a half later of interfering with any data bound controls, of which this article was written about the first.</p></font><font size="2"><p>After having a chance to reflect upon all of these quirks I have found a workaround that allows me to maintain the header's ability to dynamically set the appropriate stylesheet path via Page.ResolveUrl().&nbsp; The solution was to only call DataBind() on the header.&nbsp; In the above code block for OnInit you need to change the Page.DataBind() to Page.Header.DataBind().&nbsp; Now, the code will parse the header, set it to allow more controls to be added and not interfere with databound elements on your page.</p></font><font size="2"><p>Hopefully this article was of help to somebody.</p></font>

<pre class="csharpcode">
<span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnInit(EventArgs e)
{
    Page.DataBind();
    <span class="kwrd">base</span>.OnInit(e);
}
</pre>

]]></description>
         <link><![CDATA[http://www.PNWDC.com/Blog/BlogArticle.aspx?ArticleID=2]]></link>
         <pubDate>Wed, 11 Apr 2007 10:10:58 GMT</pubDate>
      </item>
   
         </channel>
      </rss>  
   

