


      <rss version="2.0">
         <channel>
            <title><![CDATA[ RTHilton.com - Technology, Photography, Passion]]></title>
            <link>http://www.RTHilton.com</link>
            <description>Ryan Hilton is a freelance Web/Application Developer and Systems Consultant residing in Vancouver, WA.  He is passionate about Web Technologies, .Net and Photography.  He would be more passionate about Graphics and Graphic Design, though he really isn't that great at them, no matter how hard he tries.</description>
            <copyright>Copyright 2009 Ryan T. Hilton</copyright>
   
      <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.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=27]]></link>
         <pubDate>Thu, 01 Apr 2010 13:39:51 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Paged SQL Queries]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[This is mostly for my own memory, a procedure that returns paged results:<br /><br /><div>CREATE PROCEDURE [Messaging].[GetMessagesForUserPaged]</div><div>(</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>@MessageTo uniqueidentifier,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>@StartRow int,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>@Count int,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>@SortExpression nvarchar(100)</div><div>)</div><div>AS</div><div><br /></div><div>IF LEN(@sortExpression) = 0</div><div>&nbsp;&nbsp; &nbsp;SET @sortExpression = 'DateSent DESC'</div><div><br /></div><div>DECLARE @sql nvarchar(4000)</div><div><br /></div><div>SET @sql = 'SELECT&nbsp;</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>MessageID,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>MessageFrom,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>MessageTo,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>DateSent,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>Unread,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>Subject,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>Body,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>MessageTypeID,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>RowNumber</div><div>FROM (</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>SELECT&nbsp;</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>MessageID,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>MessageFrom,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>MessageTo,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>DateSent,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>Unread,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>Subject,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>Body,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>MessageTypeID,</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>ROW_NUMBER() OVER (ORDER BY ' + @SortExpression + ') AS RowNumber</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>FROM [Messaging].[Messages]</div><div><span class="Apple-tab-span" style="white-space: pre; "></span>WHERE MessageTo = @MessageTo</div><div>) AS PagedRecords</div><div>WHERE RowNumber &gt; ' + CONVERT(nvarchar(10), @StartRow) + ' AND RowNumber &lt;= (' + CONVERT(nvarchar(10), @StartRow) + ' + ' + CONVERT(nvarchar(10), @Count) + ')'</div><div><br /></div><div>-- Execute the SQL query</div><div>EXEC sp_executesql @sql</div>]]></description>
         <link><![CDATA[http://www.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=26]]></link>
         <pubDate>Mon, 01 Mar 2010 11:26:19 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.RTHilton.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.RTHilton.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.RTHilton.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.RTHilton.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.RTHilton.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.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=19]]></link>
         <pubDate>Mon, 20 Oct 2008 15:21:50 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Enterprise Backup Vendors - How Much Should They Bend Over for Business?]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[I am currently working on a project that has been spanning almost a year trying to find an Enterprise-Ready backup solution that will work within our organization as well as serve as an example for other, similar organizations. I believe I have found the solution, but politics on both ends have managed to squeeze themselves into the mix.<br /><br /> I have currently been entertaining CommVault. It has an excellent reputation, has been around a while, supports all of the features we require (Backup, CDP, Archiving, DR, CDP, eDiscovery, etc.) Now, for my little chunk of the network alone this product will cost around $50k to implement after all is said and done. For our organization we would be looking at upper six figures, possibly seven.<br /><br /> Now, here is where the politicians come in to play. We have already purchased some of their CDP components and are using them in production. We wanted to evaluate the rest of their backup line in our environment to supplant our existing ArcServe configuration, because it is far from being on par with our needs. CommVault was able to come to the table with two options, try it for 30 days or lease to own for a year. What we were looking for was a 90-180 day window where we could install the software, get it stable and running and proven, document the process and invite the other stakeholders in for a Q&amp;A session with demo. At this point a decision would be made by all involved to either move forward with a purchase or to find another product.<br /><br /> Another political issue with this is how the project will be funded. If we were to move forward with this project on the lease method there is no guarantee that six months into the lease we will have the budget available to continue. On the other hand if we were to just purchase the trial part of the project independently of our headquarters then we would not be reimbursed when they purchase for themselves or for all of the other groups, taking a hefty chunk out of our operating budget.<br /><br /> We won't know for certain what product will be a clear winner until we have it up and running and proven with production data in a production environment, as that is when things always tend to break. A clear winner may not be known based on whether the product is capable of doing the job or not, but whether the rest of the team agrees that it is the product that they want to be supporting for the next 5-10 years. A standardized Enterprise-wide system like this isn't a small undertaking and there are many things to consider.<br /><br /> So, are we demanding too much or are we justified in our asking for an extended evaluation period of roughly $50k worth of software? Or, should we just fork over the money, hope the product works and move on? Should I forget about a product that I consider best of breed from what I've seen and turn to products that I could care less for, such as Symatec's product line that now has &quot;Replication 80% integrated into the interface&quot; thanks to their acquisitions?<br /><br /> I have lost most of my momentum on this project because of these issues and I am struggling to keep focused on it to come up with a solution that will move us away from the nightmare that is Computer Associates and not present us with new nightmares. I would really love to be able to sleep at night knowing that my backups are working and that I will be able to recover from them, no matter how bad the crash may be.]]></description>
         <link><![CDATA[http://www.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=16]]></link>
         <pubDate>Fri, 10 Oct 2008 20:35:14 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.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=15]]></link>
         <pubDate>Fri, 10 Oct 2008 18:38:21 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Apple's Blinking Folder of Questionability]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[I have a new phrase, the &quot;Blinking Folder of Questionability&quot; which from what I've discovered is a Very Bad Thing™. I am hoping to have it back up and running soon, as this is the first problem that I have had with this in two years, and still on the original load, I would never trust a PC with that kind of uptime unless it was running a server OS or <span style="font-weight: bold; ">very</span>&nbsp;well maintained, something that nobody I know does.<br /> <br />  ]]></description>
         <link><![CDATA[http://www.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=14]]></link>
         <pubDate>Tue, 30 Sep 2008 20:35:01 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.RTHilton.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.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=12]]></link>
         <pubDate>Fri, 18 Jan 2008 16:36:42 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Adventures In Replacing a Bathroom Vent]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[In our wonderful old house, which I love, we needed to replace our old bathroom fan because it just wasn't doing anything and it was very loud.&nbsp; Another joy of it, was that we had one switch in the bathroom, to control the light and fan together, so if the light was on, so was that fan.&nbsp; To Home Depot we go!<br /><br />We found a pretty decent fan, low noise, decent air movement (60 CFM for a 30'<sup>2</sup> bathroom should do) for about $30.00.&nbsp; I started off pulling off the old fan and placing the new one over the existing hole, set the screws and started to rewire.&nbsp; I wanted to wire in 2 more switches, one for&nbsp;the fan and one for the fan's light.&nbsp; Pretty simple, right?<br /><br />Our electrical in this house is beyond substandard and I have been just aching to rewire, but money and time have been lacking.&nbsp; The wiring goes from the breaker box in the garage, outside to a junction box, into the kitchen wall, up into the attic to a junction box over the kitchen light which feeds the kitchen, then to a junction box over the bathroom which feeds the bathroom, then to the &quot;master&quot; bedroom's junction box, then to the second bedroom's junction box, then to the living room...&nbsp; You get the point, one circuit, 90% of the house, not good when you have toys.<br /><br />I began fishing the wires through the walls and trying to replace all the bathroom wiring, gutting what I can as I go.&nbsp; All wires in the house our two conductor, oil cloth wrapped and then metal armored.&nbsp; It is hard-core old school in a very bad way.&nbsp; I guess I should be thankful it is not nob and tube?&nbsp; The wires going from the switch up would not come out, so I had to snip those.&nbsp; I could see at least two beefy staples holding it in.&nbsp; I ran 3 new wires down to a new two-gang switchbox and life was good there.&nbsp; I then wired up everything into a new junction box for the fan circuit, which is now mostly dedicated per code.&nbsp; This circuit home-runs to the panel and ties in to the same circuit that it was using before because I have no more breakers, an issue I'm sure I'll be writing about soon.<br /><br />Now, the outlet on the wall in the bathroom, which of course is not GFCI, also feeds an outlet on the opposite side of the wall.&nbsp; We like to call that room the kitchen, though it is more of a glorified broom closet with a stove.&nbsp; Run another wire, tie it in, new home run, rinse, lather, repeat.<br /><br />Ok, all of the electrical is done.&nbsp; I'm sure I missed a few problems, but that pretty much sums up my troubles.&nbsp; Now, time to yank the old cover from the plaster ceiling.&nbsp; Oops, was that a bunch of plaster that just fell on my head?&nbsp; Yep.&nbsp; Seems as though the cover was the only thing holding up the water damanged plaster surrounding the vent.&nbsp; I also noticed some water in the roof rafters above the bathroom, not a good sign.&nbsp; Probably a good thing we are doing this replacement.&nbsp; So, now I have to either fix a plaster ceiling or replace it with sheetrock.&nbsp;&nbsp; We opted for sheetrock, time to call the sheetrock master, my father in-law.&nbsp; We get the gang together, before they arrive I hammer down all the plaster, clean up, cut the lath down prep for drywall.&nbsp; They show up, we start measuring and getting ready to put up the drywall.&nbsp; That's when my granddad in-law shows up, and asks if we want a sky light, his treat.&nbsp; Feeling bad, knowing how much the roof-dome, shiny tube, sky lights are, I try to refuse but he iniststs.<br /><br />So, I got the sky light and while they were framing in the bathroom for drywall, I got to hang in the rafters cutting a big hole in my roof...in the rain.&nbsp; Roof cut, time to go to the outside.&nbsp; Our roof is an 8-12 or a 9-12 pitch, which translates to pretty darn scary.&nbsp; No rope, wet roof, lets rock!&nbsp; We made a make-shift harness using an extension cord, a bowline knot, a cast-iron vent pipe on the roof and a brother in-law couter weight.&nbsp; Mounted the dome, tarred up my hands, and completed the outside portion with a little fright but all-in-all pretty much no problems.&nbsp; The hole ligned up nice, the shingles moved out of the way to let it slide up, and nobody got hurt.<br /><br />So, right now we have a drywalled bathroom with all new electrical, GFCI outlets, a new fan/light combo, a skylight and a new run for the kitchen that keeps it off the same circuit.&nbsp; I've ran three new home runs of wire to the panel, even though I joined them in a junction box for the time being.&nbsp; All that we need to do now is finish the drywall mudding, texture, paint and admire.&nbsp; All of this work to get a new fan installed.&nbsp; I love this house.]]></description>
         <link><![CDATA[http://www.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=11]]></link>
         <pubDate>Sun, 30 Dec 2007 22:29:27 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Microsoft Office Live Pains]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[I'm usually not one to rant, but I have got to get this out.&nbsp; A while ago I signed up for Microsoft Office Live, the free version.&nbsp; I got the free domain name, played around with the templates, and found it to be completely useless for anybody with half a clue.&nbsp; My domain sat, unused.<br /><br />I've been working with a client on developing a website, and came to discover that he was using Office Live.&nbsp; After looking at their features, we determined it would be best to archive his e-mail and move off of Office Live.&nbsp; This is where things get fun.<br /><br /><span style="font-weight: bold">Why did we choose to move away from Office Live?</span><br />You are locked in to either their templates or&nbsp;you can use &quot;3rd Party Developer Tools.&quot;&nbsp; This is Microsoft's way of saying you can upload straight HTML content, no forms, no ASP.NET, no capability for a form for visitors to leave you a message.&nbsp; Come on, this is 2007 people, you would have to be completely ignorant to think that anybody would want this.<br /><br /><span style="font-weight: bold">Moving the domain name</span><br />This is the beggest problem.&nbsp; You have to cancel your account to move your domain name to your registrar of choice.&nbsp; Which means you have to cancel your account, unlock your domain, perform the transfer, wait for approval and then, after all that, you can redirect to your mailservers.&nbsp; During the process your mail is pointing to a deactivated account.&nbsp; But hey, who still uses e-mail right?<br /><br />Microsoft is refusing to relinquish the domain registration key, used to take control of the domain, without the cancellation but is now claiming you can &quot;point your domain out&quot; without the keys.&nbsp; I've done a bit of DNS work in my time, including enough manual editing of named.conf and zone files from bind 4 and up.&nbsp; I'm assuming that they are saying I can change my CNAME, A and PTR records, but if that is the case then why not just let me take over my domain?<br /><br />Their excuse is that I can cause serious problems.&nbsp; Sure, if I were an idiot I might screw something up, but nothing too serious that can't be undone fairly quickly.&nbsp; The only problem that I am creating is that I can now easily migrate off of a Microsoft service.<br /><br />Don't get me wrong, I'm not trying to Microsoft bash here.&nbsp; I have become a supporter of Microsoft over the past few years.&nbsp; Nobody comes close to them in enterprise class network and host management.&nbsp; C# is beautiful and I wouldn't trade it for anything, it captured my heart away from PERL and PHP, my first and second love.&nbsp; But this is absolutely absurd.<br /><br />Microsoft, please lighten up and give users some freedom, we aren't all stupid.&nbsp; Some of us are capable of thinking for ourselves and, to top it off, we can even form coherent sentences without clippy.&nbsp; Give us our domains and a way to manage them, or at least leave us as delegates with our registrar, MelbourneIT.]]></description>
         <link><![CDATA[http://www.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=10]]></link>
         <pubDate>Fri, 30 Nov 2007 21:12:11 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Backup Software Evaluation]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[&nbsp;At work recently we had a pretty nasty failure.&nbsp; One of our RAID arrays suffered a two disk failure.&nbsp; Anybody who knows about RAID arrays knows that this is a very bad thing. I would have just rebuilt the array with new drives and restored from backup, except that our backups were rather...incomplete.&nbsp; Now, I should have had these configured and validated, but who validates their backups anyways?<br /><br />The reason there were no good backups, while a total cop-out for me, is our former DBA.&nbsp; He setup full backups on a schedule and then transaction log dumps every hour.&nbsp; The problem with this is that he had an error in his schedules and the last full backup was a year ago, though we did have transaction logs from the past two weeks, lucky us.&nbsp; Another problem with this scenario is that it has to be setup for <span style="font-weight: bold">every</span> database, so all those databases that we created over the past year, guess what, no backups.&nbsp; There was one exception to this.&nbsp; That would be the backup that <span style="font-weight: bold">I </span>created.<br /><br />Now, I do take full responsibility for not having accurate backups, because I should have installed a SQL backup agent on this server, it was the only one that I didn't do this with after the previous DBA left us.&nbsp; I didn't trust his methods, and this vendicated me a little bit.&nbsp; Unfortunately, vendication made me look bad,&nbsp;but such is life.<br /><br />Our current backup solution is Computer Associates' Brightstore ArcServe backup, r11.5 SP3 to be exact.&nbsp; The problem with this software is that it really sucks.&nbsp; The Tape Engine crashes all the time, media pools don't release media on file system devices per your retention schedule, and failures need restarted from the beginning.<br /><br />The two main&nbsp;vendors we are looking at include:<br />Symantec NetBackup, PureDisk and Enterprise Vault<br />CommVault Galaxy<br /><br />Some of the features that I'm looking at include:<br />Synthetic Fulls<br />Job restarting at the failure point<br />Destination media failover<br />Reporting<br />Full array of agents<br />Exchange document level backups and restores<br />Disk to Disk to Tape<br /><br />So far I'm leaning towards CommVault.&nbsp; Their software is highly integrated, since it was all written in house and none of it required later integration.&nbsp; Symantec likes to talk about how &quot;integrated&quot; their software is.&nbsp; Feature A is now 50% integrated, whereas feature B is a whopping <span style="font-weight: bold">80%</span> integrated.&nbsp; Seriously, backups are crucial to the survivability of a business, I need tight integration.<br /><br />I'll be posting more information as I nail down the specifics and any issues we run into.]]></description>
         <link><![CDATA[http://www.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=9]]></link>
         <pubDate>Thu, 04 Oct 2007 21:49:14 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[The Pains of Remodeling]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[For those of you who don't know, my wife, son and I have purchased our first house.&nbsp; It is a cute 2 bed, 1 bath bungalow with a garage.&nbsp; The house portion is 528 square feet and the garage isn't too bad in size.&nbsp; This little house sits on a nice 0.14 acres at the edge of alphabet city in Vancouver.<br /><br />We knew that there were some things wrong with it.&nbsp; Let me rephrase that.&nbsp; We knew that there were some things that needed &quot;updated&quot;.&nbsp; This included electrical, plumbing, adding a fence and removing the plaster and replacing it with drywall.&nbsp; None of where are a real easy endeavor, especially when you think you can just do it, and don't know you need a permit.&nbsp; Oops.<br /><br />The other night I went to install a ceiling fan.&nbsp; No, I don't need a permit for that, just listen.&nbsp; The wiring in this house has no ground and is the old cloth wrapped wire.&nbsp; Since I was installing this into an existing fixture the wires were already quite worn from age, plus the heat from the light after all these years.&nbsp; After I started pushing the wires back up the insulation began to crumble in my hands.&nbsp; Hmm, bare wire in the ceiling.&nbsp; Anybody want to come flip the lightswitch that controls it?&nbsp; Well, don't.&nbsp; I will kick you and then&nbsp;have my son bite you with his tooth.<br /><br />This all prompted me to begin making headway to replace the electrical wiring, and with that the breaker box.&nbsp; Bringing in 20amp throughout the house and 12 guage wire, with a ground even.&nbsp; So, I called up an electrician buddy of my brother and he stopped by to advise of what I needed to do.&nbsp; It involved everything from bringing new cable from the service mast down, new cable from the meter to the panel and the actual panel swap itself.<br /><br />So, I start working on clearing out the attic, which brings forth all sorts of <a href="http://www.rthilton.com/Blog/BlogArticle.aspx?ArticleID=7">treasure</a>.&nbsp; This is when I discovered the old rock wool insulation hiding underneath all of the beautiful white, newer insulation.&nbsp; A great thing to discover in an attic on a 90+ degree day wearing cover-alls.&nbsp; The newer insulation was installed in 1997 per a sheet I found stapled to the gable, and when they installed it they failed to remove the old insulation and, to top things off, tons of debris from the old shake roof.&nbsp; So, I decided it was time for it to go.&nbsp; I have been scooting all of the newer insulation to a &quot;box&quot; I made and then shovelling up the old stuff, bagging it and vacuuming what is left.&nbsp; I'm about 75% done and I have 20+ 30 gallon bags of old insulation.&nbsp; If anybody wants it, they can come take it off my back lawn, free of charge.<br /><br />I've now discovered the joy of permits.&nbsp; I guess you are supposed to get one every time you blow your nose more than four times, even though it is <span style="font-weight: bold">your</span> house.&nbsp; I'm sure they have a good reason, but I have good reason not to get one.&nbsp; Like, not having the money for the permit and the work.&nbsp; Not having the money to bring everything up to code.&nbsp; From what I've heard, just to rewire the house to replace the old wiring I have to install hard-wired smoke detectors in every bedroom.&nbsp; That may not sound like much, but that's an easy $50 cost that I just don't have right now, remember, I can't afford the permit.<br /><br />I would do all of this without the permit, the biggest problem is when I go to sell and when I need the PUD to come out and remove the meter and then replace it.&nbsp; For some reason they want to know that I'm not going to blow their transformers with a short.&nbsp; For crying out loud people, you have 3 wires to worry about, I'm not an idiot nor am I careless/stupid when we're talking 200 amp, or even 15 amp for that matter.&nbsp; Piss off!<br /><br />So, these are the wonderful woes I have been encountering so far.&nbsp; We knew it would be work, we were just hoping to have a month or two of breathing room before the work started.&nbsp; Most of our stuff is still in the garage boxed up.&nbsp; We may not have anywhere to put it, but we haven't even had time to try.&nbsp; Oh well, the house is ours (the banks) and we all love it anyways.&nbsp; It has all been a great experience so far and I wouldn't trade it for anything.&nbsp; This house is cute and fun and all this work will pay off greatly in the end.]]></description>
         <link><![CDATA[http://www.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=8]]></link>
         <pubDate>Tue, 07 Aug 2007 22:00:13 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[Melomite - The Magic Crystal]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[While cleaning out the attic of our wonderfully cute new house I discovered quite them geeky gem.&nbsp; A cardboard piece of cardstock paper with two staples and pretty old looking.&nbsp; Here is the text of the &quot;Front&quot;:<br /><br />Melomite<br />The Magic Crystal<br />Radio Crystal 25c<br /><br />Super Sensitive<br />Guaranteed&nbsp;&nbsp;&nbsp;&nbsp; Guarantee&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Tone Tested<br /><br />This genuine &quot;Melomite&quot;<br />radio crystal is made of selected radio mineral and is guaranted to give perfect satisfaction&quot;<br /><br />Alva F. Allen<br />Clinton. MO., U.S.A<br /><br />The &quot;Back&quot; is a bit more complex and I won't type it out here, the gist of it is a basic set of instructions on how to make your own radio, which requires 4 1/2&quot; x 3&quot; wood board, coil of wire, ground, arial antenna (100-200ft is best), posts, tack and the crystal.<br /><br />The copyright on the back is from 1932.&nbsp; This house was built in 1941 so it is a good chance that it has been up there since its inital purchase or around there.&nbsp; I did notice another piece of paper, which until after I found this meant nothing, so I vacuumed it up (sorry).&nbsp; It was a shipping sheet from the same city/state, though whether it is related I am unsure.&nbsp; Oh well.&nbsp; I guess I will never know.<br /><br />If anybody knows any more about this, <a href="mailto:rthilton@gmail.com?Subject=Melomite+Crystal">let me know</a>.&nbsp; A search of Melomite Crystal in google yields nothing.&nbsp; How sad.]]></description>
         <link><![CDATA[http://www.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=7]]></link>
         <pubDate>Tue, 07 Aug 2007 21:36:46 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.RTHilton.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.RTHilton.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.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=4]]></link>
         <pubDate>Tue, 17 Jul 2007 14:40:35 GMT</pubDate>
      </item>
   
      <item>
         <title><![CDATA[New Website Online]]></title>
         <author><![CDATA[RTHilton]]></author>
         <description><![CDATA[In case you haven't noticed, my new website is now online.&nbsp; After about a month of redesigning it I think I am finally happy with how it looks.&nbsp; I am also now using several new technologies to help make updates go a little smoother and I've broken apart some of the code to make it more modular so that I can reuse it on other sites.<br /><br />Here are some of the technologies that I am using on my pages:<br /><br />ASP.NET<br />Master Pages<br />Lightbox Javascript by Lokesh Dhakar<br />SQL 2000 (Will you please update GoDaddy?)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Heavy use of stored procedures, no raw SQL queries in the code.<br />.Net DataSets<br />XML SiteMapProvider<br />Two custom SiteMapProviders (Blog, Gallery)<br />GlobalRadioButton by MetaBuilders<br /><br />Feel free to provide feedback of the new look by sending me an <a href="mailto:rthilton@gmail.com?Subject=New+Website+Design+Feedback">e-mail</a>.]]></description>
         <link><![CDATA[http://www.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=3]]></link>
         <pubDate>Sun, 08 Jul 2007 00:35:51 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.RTHilton.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.RTHilton.com/Blog/BlogArticle.aspx?ArticleID=2]]></link>
         <pubDate>Wed, 11 Apr 2007 10:10:58 GMT</pubDate>
      </item>
   
         </channel>
      </rss>  
   

