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

<channel>
	<title>FuGeRTech &#187; Web Development</title>
	<atom:link href="http://fugertech.com/category/technology/web-dev/feed/" rel="self" type="application/rss+xml" />
	<link>http://fugertech.com</link>
	<description>Enduring, Sustainable Results</description>
	<lastBuildDate>Mon, 10 Jan 2011 23:09:00 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Migrating Subversion Repositories</title>
		<link>http://fugertech.com/2010/05/17/migrating-subversion-repositories/</link>
		<comments>http://fugertech.com/2010/05/17/migrating-subversion-repositories/#comments</comments>
		<pubDate>Mon, 17 May 2010 22:44:56 +0000</pubDate>
		<dc:creator>Tony</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[repository]]></category>
		<category><![CDATA[subversion]]></category>
		<category><![CDATA[svn]]></category>
		<category><![CDATA[version control]]></category>

		<guid isPermaLink="false">http://fugertech.com/?p=524</guid>
		<description><![CDATA[When it rains it pours. This is the most action the blog has seen in a while. Today&#8217;s issue: migrating a sub-directory from one subversion repository to another without svnadmin access to either repo. Problem My client has a subversion repository used to store all the company applications. I have a sub-directory in this repo <a href='http://fugertech.com/2010/05/17/migrating-subversion-repositories/' class='excerpt-more'>[...]</a>]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify;">When it rains it pours. This is the most action the blog has seen in a while. Today&#8217;s issue: migrating a sub-directory from one subversion repository to another without svnadmin access to either repo.</p>
<p><span id="more-524"></span></p>
<h2>Problem</h2>
<p style="text-align: justify;">My client has a subversion repository used to store all the company applications. I have a sub-directory in this repo storing the code for my web applications. It includes CakePHP based web applications that have many folders with the svn:ignore property set to avoid tmp and other folders that have data that is not required in the repo. I do not have access to perform an svnadmin dump.</p>
<h2 style="text-align: justify;">Solution</h2>
<p style="text-align: justify;">After a few minutes of searching Google and contemplating a solution, I decided to just write some Bash scripts to solve this problem. This is the procedure that I came up with:</p>
<ol>
<li>Copy the old working sub-directory into the new working sub-directory</li>
<li>Remove all .svn from the old working sub-directory in the new working sub-directory</li>
<li>Change to the old working sub-directory in order to retrieve relative paths</li>
<li>Get a list of directories that have svn:ignore properties and put them into a file</li>
<li>Iterate through the list of directories</li>
<li>Establish a filename for storing the svn:ignore property information</li>
<li>Run the svn propget svn:ignore on the old working directory and output to the filename generated in step 3</li>
<li>Run the svn propset svn:ignore -F [filename] on the new working directory</li>
<li>Perform basic clean-up of temp files</li>
<li>Change back to directory the user was using when the script was called</li>
<li>Exit</li>
</ol>
<pre>#!/bin/bash

HOME="/path/to/home"
SRC="/path/to/old/working/directory"
DST="/path/to/new/working/directory"

cp -r "${SRC}" "${DST}"
find "${DST}" -type d -name .svn -exec rm -rf {} \;

pushd "${SRC}"

for i in `find . -type d | grep -v .svn`; do svn proplist $i | grep ^Prop | sed "s/^[^']*'\([^']*\)':/\1/"; done &gt; "${HOME}/tmp"

while read line; do
 FILENAME=`echo $line | sed 's#/#_#g'`
 svn propget svn:ignore "${SRC}/$line" &gt; "${HOME}/${FILENAME}.svnignore"
 svn propset svn:ignore -F "${HOME}/${FILENAME}.svnignore" "${DST}/$line"
 rm "${HOME}/${FILENAME}.svnignore"
done &lt; "${HOME}/tmp"
rm tmp

popd

exit 0
</pre>
<h2 style="text-align: justify;">Warning</h2>
<p style="text-align: justify;">This script was run in a Cygwin environment and has not been tested in other Unix or Linux environments. It makes the the assumption that the properties that are found are ONLY svn:ignore properties. Any others could cause issues. I only had svn:ignore properties set. At first I ran the first half of the real work in this script manually to see the outcome one step at a time. I later compiled into this single script, but without fully testing it again as my repo was already set and I did not want to destroy it.</p>
<p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Ffugertech.com%2F2010%2F05%2F17%2Fmigrating-subversion-repositories%2F&amp;title=Migrating%20Subversion%20Repositories" id="wpa2a_2">Share/Save</a></p>]]></content:encoded>
			<wfw:commentRss>http://fugertech.com/2010/05/17/migrating-subversion-repositories/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CakePHP Sessions in Applications</title>
		<link>http://fugertech.com/2010/05/12/cakephp-sessions-in-applications/</link>
		<comments>http://fugertech.com/2010/05/12/cakephp-sessions-in-applications/#comments</comments>
		<pubDate>Wed, 12 May 2010 21:04:50 +0000</pubDate>
		<dc:creator>Tony</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://fugertech.com/?p=520</guid>
		<description><![CDATA[Preface I have yet to run across anyone in the Google search result world that is running a CakePHP application that contains multiple sub-applications. I have been developing multiple web-based applications in CakePHP that all reside in a single virtual host. The &#8220;master&#8221; application controls access to these applications. Each application is a sub-directory of <a href='http://fugertech.com/2010/05/12/cakephp-sessions-in-applications/' class='excerpt-more'>[...]</a>]]></description>
			<content:encoded><![CDATA[<h2>Preface</h2>
<p>I have yet to run across anyone in the Google search result world that is running a CakePHP application that contains multiple sub-applications. I have been developing multiple web-based applications in CakePHP that all reside in a single virtual host. The &#8220;master&#8221; application controls access to these applications. Each application is a sub-directory of the master APP/webroot (actually contains symlinks pointing back to the applications&#8217; APP/webroot and Apache +FollowSymLinks).</p>
<h2>Problem</h2>
<p>My Session-&gt;flash notices do not work when I call a redirect within these sub-applications. If I do NOT redirect, then the flash appears as expected. The moment I redirect, it&#8217;s gone.<span id="more-520"></span></p>
<h2>Solution</h2>
<p>I went through a lot of &#8220;solutions&#8221; according to people experiencing the similar problems. In doing so, I noticed that I had all the Session.cookie parameters configured the same in every APP/config/core.php file.</p>
<pre>Configure::write('Session.cookie', 'CAKEPHP');
</pre>
<p>I changed this for one of my sub-applications to something unique. Voila! It worked. I have not gone into the details as to why, but it seems apparent to me that the Session.cookie must be unique across the &#8220;master&#8221; application and any sub-applications residing within the &#8220;master&#8221; app. This allows individual session cookies to be started for each application to use/access. The &#8220;master&#8221; app is probably wiping the slate clean behind the scenes.</p>
<pre>Configure::write('Session.cookie', 'APP_NAME');</pre>
<p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Ffugertech.com%2F2010%2F05%2F12%2Fcakephp-sessions-in-applications%2F&amp;title=CakePHP%20Sessions%20in%20Applications" id="wpa2a_4">Share/Save</a></p>]]></content:encoded>
			<wfw:commentRss>http://fugertech.com/2010/05/12/cakephp-sessions-in-applications/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CakePHP Custom Queries and Concatenate</title>
		<link>http://fugertech.com/2010/05/11/cakephp-custom-queries-and-concatenate/</link>
		<comments>http://fugertech.com/2010/05/11/cakephp-custom-queries-and-concatenate/#comments</comments>
		<pubDate>Tue, 11 May 2010 17:01:40 +0000</pubDate>
		<dc:creator>Tony</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://fugertech.com/?p=492</guid>
		<description><![CDATA[Problem I recently ran into an issue where I found it much more efficient to write a custom query to gather the data I needed to perform some processing than it was to utilize CakePHP&#8217;s model/controller normal data accessing functionality. At first, I wanted to make sure that all of my related tables gathered all <a href='http://fugertech.com/2010/05/11/cakephp-custom-queries-and-concatenate/' class='excerpt-more'>[...]</a>]]></description>
			<content:encoded><![CDATA[<h2 style="text-align: justify;">Problem</h2>
<p style="text-align: justify;">I recently ran into an issue where I found it much more efficient to write a custom query to gather the data I needed to perform some processing than it was to utilize CakePHP&#8217;s model/controller normal data accessing functionality.</p>
<p style="text-align: justify;">At first, I wanted to make sure that all of my related tables gathered all of the data from each other. This created numerous objects filled with data that I may or may not use. It also resulted in the need to loop through every array multiple times to gather the final data required.</p>
<p style="text-align: justify;">So, I decided to make a custom query  in the model that would handle joining all the tables into one array. I ran into trouble where fields were being grouped into their respective models as expected by CakePHP&#8217;s model class. This caused a headache to access the data properly again.<span id="more-492"></span></p>
<h3 style="text-align: justify;">Original Custom Query</h3>
<pre>SELECT
	environments.id,
	environments.name,
	deploy_files.name,
	web_services.name,
	CONCAT(web_services.name,\'.\',users.name,\'=\',users.password) line
FROM
	deploy_files,
	environments,
	users,
	web_services,
	users_web_services,
	users_environments,
	web_services_deploy_files
WHERE
users.id = users_web_services.user_id
AND web_services.id = users_web_services.web_service_id
AND users.id = users_environments.user_id
AND environments.id = users_environments.environment_id
AND web_services.id = web_services_deploy_files.web_service_id
AND deploy_files.id = web_services_deploy_files.deploy_file_id
AND users.active = 1
AND web_services.active = 1
AND deploy_files.active = 1
AND environments.active = 1
ORDER BY
	environments.name,
	deploy_files.name,
	web_services.name,
	users.hidden DESC,
	users.name</pre>
<h3>Original Output</h3>
<pre>array (
  0 =&gt;
  array (
    'environments' =&gt;
    array (
      'id' =&gt; '1',
      'name' =&gt; 'Production',
    ),
    'deploy_files' =&gt;
    array (
      'name' =&gt; 'test.login',
    ),
    'web_services' =&gt;
    array (
      'name' =&gt; 'WebService',
    ),
    0 =&gt;
    array (
      'line' =&gt; 'WebService.security=on',
    ),
  )
)</pre>
<p style="text-align: justify;">This output is usable, but I would have preferred to have everything simplified a bit and not grouped into sub associative arrays</p>
<h2 style="text-align: justify;">Solution</h2>
<p style="text-align: justify;">I noticed the results of the CONCAT in the original query were in their own sub-array (0, zero). So, I took every column and put them in their own CONCAT and gave them an alias.</p>
<h3 style="text-align: justify;">New Custom Query</h3>
<pre>SELECT
	CONCAT(environments.id) env_id,
	CONCAT(environments.name) env_name,
	CONCAT(deploy_files.name) file_name,
	CONCAT(web_services.name) ws_name,
	CONCAT(web_services.name,\'.\',users.name,\'=\',users.password) line
FROM
	deploy_files,
	environments,
	users,
	web_services,
	users_web_services,
	users_environments,
	web_services_deploy_files
WHERE
users.id = users_web_services.user_id
AND web_services.id = users_web_services.web_service_id
AND users.id = users_environments.user_id
AND environments.id = users_environments.environment_id
AND web_services.id = web_services_deploy_files.web_service_id
AND deploy_files.id = web_services_deploy_files.deploy_file_id
AND users.active = 1
AND web_services.active = 1
AND deploy_files.active = 1
AND environments.active = 1
ORDER BY
	environments.name,
	deploy_files.name,
	web_services.name,
	users.hidden DESC,
	users.name</pre>
<h3 style="text-align: justify;">New Output</h3>
<pre>array (
  0 =&gt;
  array (
    0 =&gt;
    array (
      'env_id' =&gt; '1',
      'env_name' =&gt; 'Production',
      'file_name' =&gt; 'test.login',
      'ws_name' =&gt; 'WebService',
      'line' =&gt; 'WebService.security=on',
    ),
  )
)</pre>
<p style="text-align: justify;">Now everything is inside the single 0 sub-array. The biggest problem I see with this is that I have concocted a custom query that breaks any CakePHP naming conventions. My controller and other data consuming components now rely on <em>KNOWING</em> these specific columns from this query. However, it is a large query that generates a lot more data than has been shown in these examples. Therefore, I think it is more efficient and memory-saving than the CakePHP natural methods or even a basic custom query like the original custom query above.</p>
<p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Ffugertech.com%2F2010%2F05%2F11%2Fcakephp-custom-queries-and-concatenate%2F&amp;title=CakePHP%20Custom%20Queries%20and%20Concatenate" id="wpa2a_6">Share/Save</a></p>]]></content:encoded>
			<wfw:commentRss>http://fugertech.com/2010/05/11/cakephp-custom-queries-and-concatenate/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SharePoint Designer 2007 is Free</title>
		<link>http://fugertech.com/2009/04/14/sharepoint-designer-2007-is-free/</link>
		<comments>http://fugertech.com/2009/04/14/sharepoint-designer-2007-is-free/#comments</comments>
		<pubDate>Tue, 14 Apr 2009 14:59:39 +0000</pubDate>
		<dc:creator>Tony</dc:creator>
				<category><![CDATA[SharePoint]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[WSS 3.0]]></category>

		<guid isPermaLink="false">http://fugertech.com/?p=137</guid>
		<description><![CDATA[Microsoft is now offering SharePoint Designer 2007 for free!]]></description>
			<content:encoded><![CDATA[<p>Microsoft is now offering <a href="http://www.microsoft.com/downloads/details.aspx?displaylang=en&amp;FamilyID=baa3ad86-bfc1-4bd4-9812-d9e710d44f42" target="_blank">SharePoint Designer 2007 for free</a>!</p>
<p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Ffugertech.com%2F2009%2F04%2F14%2Fsharepoint-designer-2007-is-free%2F&amp;title=SharePoint%20Designer%202007%20is%20Free" id="wpa2a_8">Share/Save</a></p>]]></content:encoded>
			<wfw:commentRss>http://fugertech.com/2009/04/14/sharepoint-designer-2007-is-free/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SharePoint Log Files</title>
		<link>http://fugertech.com/2009/01/26/sharepoint-log-files/</link>
		<comments>http://fugertech.com/2009/01/26/sharepoint-log-files/#comments</comments>
		<pubDate>Mon, 26 Jan 2009 15:30:01 +0000</pubDate>
		<dc:creator>Tony</dc:creator>
				<category><![CDATA[SharePoint]]></category>
		<category><![CDATA[Administration]]></category>
		<category><![CDATA[Diagnostic Logging]]></category>
		<category><![CDATA[LinkedIn]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Microsoft SQL Server 2005]]></category>
		<category><![CDATA[Operations]]></category>
		<category><![CDATA[Trace Log]]></category>
		<category><![CDATA[WSS 3.0]]></category>

		<guid isPermaLink="false">http://www.fugertech.com/?p=86</guid>
		<description><![CDATA[I am willing to bet that not too many people run into this issue, but it is still good to know. On, my client&#8217;s web server began to stall due to a full C: drive. Their server is an economy model and the C: drive is only 16GB. After some quick investigation, I found that <a href='http://fugertech.com/2009/01/26/sharepoint-log-files/' class='excerpt-more'>[...]</a>]]></description>
			<content:encoded><![CDATA[<p>I am willing to bet that not too many people run into this issue, but it is still good to know. On, my client&#8217;s web server began to stall due to a full C: drive. Their server is an economy model and the C: drive is only 16GB. After some quick investigation, I found that 3.5GB were being used in the C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12LOGS folder. This is the default location for the trace log. The path must exist on all servers in the farm, too. I decided to turn down the logging, at least temporarily, to get the server running again. To complete this task, I took the following steps:</p>
<ol>
<li>Navigate to SharePoint Central Administration</li>
<li>Click Operations</li>
<li>Click Diagnostic Logging</li>
<li>Scroll to the bottom of the page to the Trace Log section</li>
<li>Change Number of log files to <strong><span style="text-decoration: underline;">1</span></strong></li>
<li>Change Number of minutes to use a log file to <strong><span style="text-decoration: underline;">5</span></strong></li>
</ol>
<p>My client&#8217;s server is back up and running again, and I should not have to worry about the Trace Log getting out of control again.</p>
<p>The downside is that my Trace Log only goes back to the last 5 minutes of work on the server. I am researching my options here. It may not be important enough though.</p>
<p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Ffugertech.com%2F2009%2F01%2F26%2Fsharepoint-log-files%2F&amp;title=SharePoint%20Log%20Files" id="wpa2a_10">Share/Save</a></p>]]></content:encoded>
			<wfw:commentRss>http://fugertech.com/2009/01/26/sharepoint-log-files/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SharePoint: Recurring Events on Calendars</title>
		<link>http://fugertech.com/2008/11/21/sharepoint-recurring-events-on-calendars/</link>
		<comments>http://fugertech.com/2008/11/21/sharepoint-recurring-events-on-calendars/#comments</comments>
		<pubDate>Fri, 21 Nov 2008 18:48:57 +0000</pubDate>
		<dc:creator>Tony</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[SharePoint]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[calendar]]></category>
		<category><![CDATA[event]]></category>
		<category><![CDATA[LinkedIn]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[recurring]]></category>
		<category><![CDATA[WSS 3.0]]></category>

		<guid isPermaLink="false">http://www.fugertech.com/?p=60</guid>
		<description><![CDATA[While I am in the blogging mood today, I thought I would share a very helpful link. I was trying to figure out the proper way to retrieve the latest recurring events based on the current month. I Googled around for a bit before stumbling upon this: http://blogs.msdn.com/sharepoint/archive/2007/05/14/understanding-the-sharepoint-calendar-and-how-to-export-it-to-ical-format.aspx The key point is the query used: <a href='http://fugertech.com/2008/11/21/sharepoint-recurring-events-on-calendars/' class='excerpt-more'>[...]</a>]]></description>
			<content:encoded><![CDATA[<p>While I am in the blogging mood today, I thought I would share a very helpful link. I was trying to figure out the proper way to retrieve the latest recurring events based on the current month. I Googled around for a bit before stumbling upon this:</p>
<p><a href="http://blogs.msdn.com/sharepoint/archive/2007/05/14/understanding-the-sharepoint-calendar-and-how-to-export-it-to-ical-format.aspx">http://blogs.msdn.com/sharepoint/archive/2007/05/14/understanding-the-sharepoint-calendar-and-how-to-export-it-to-ical-format.aspx</a></p>
<p>The key point is the query used:</p>
<p><code>SPQuery query = new SPQuery();<br />
query.ExpandRecurrence = true;<br />
query.Query =<br />
    "&lt;Where&gt;" +<br />
        "&lt;DateRangesOverlap&gt;" +<br />
            "&lt;FieldRef Name='EventDate' /&gt;" +<br />
            "&lt;FieldRef Name='EndDate' /&gt;" +<br />
            "&lt;FieldRef Name='RecurrenceID' /&gt;" +<br />
            "&lt;Value Type='DateTime'&gt;&lt;Month /&gt;&lt;/Value&gt;" +<br />
        "&lt;/DateRangesOverlap&gt;" +<br />
    "&lt;/Where&gt;";<br />
// Look forward from the beginning of the current month<br />
query.CalendarDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); </code></p>
<p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Ffugertech.com%2F2008%2F11%2F21%2Fsharepoint-recurring-events-on-calendars%2F&amp;title=SharePoint%3A%20Recurring%20Events%20on%20Calendars" id="wpa2a_12">Share/Save</a></p>]]></content:encoded>
			<wfw:commentRss>http://fugertech.com/2008/11/21/sharepoint-recurring-events-on-calendars/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SharePoint: User Auditing</title>
		<link>http://fugertech.com/2008/11/21/sharepoint-user-auditing/</link>
		<comments>http://fugertech.com/2008/11/21/sharepoint-user-auditing/#comments</comments>
		<pubDate>Fri, 21 Nov 2008 17:26:35 +0000</pubDate>
		<dc:creator>Tony</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[SharePoint]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[audit]]></category>
		<category><![CDATA[designer]]></category>
		<category><![CDATA[groups]]></category>
		<category><![CDATA[LinkedIn]]></category>
		<category><![CDATA[membership]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[object model]]></category>
		<category><![CDATA[permissions]]></category>
		<category><![CDATA[users]]></category>
		<category><![CDATA[WSS 3.0]]></category>

		<guid isPermaLink="false">http://www.fugertech.com/?p=59</guid>
		<description><![CDATA[At my current place of employment, my development environment is limited to creating custom SharePoint tools through SharePoint Designer. I typically create an override to the OnLoad method to get my work done. Today&#8217;s topic: a simple interface for checking out who has group memberships and where they have them. I came up with the <a href='http://fugertech.com/2008/11/21/sharepoint-user-auditing/' class='excerpt-more'>[...]</a>]]></description>
			<content:encoded><![CDATA[<p>At my current place of employment, my development environment is limited to creating custom SharePoint tools through SharePoint Designer. I typically create an override to the OnLoad method to get my work done.</p>
<p>Today&#8217;s topic: a simple interface for checking out who has group memberships and where they have them. I came up with the idea after seeing the constant organizational changes and the employee role changes associated with them. So let&#8217;s talk about the two parts. If you just want the code without the breakdown, scroll to the bottom of the full article.</p>
<p><span id="more-59"></span></p>
<h2>User Lookup</h2>
<p>There are three methods to this (in addition to a base of methods that I find helpful with my custom pages). First let&#8217;s take a look at my basic header.</p>
<p><code>&lt;%@ Page language="C#"<br />
MasterPageFile="~masterurl/default.master"<br />
SmartNavigation="true"<br />
Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage,<br />
Microsoft.SharePoint,<br />
Version=12.0.0.0,<br />
Culture=neutral,<br />
PublicKeyToken=71e9bce111e9429c"<br />
meta:progid="SharePoint.WebPartPage.Document" %&gt;<br />
&lt;%@ Import Namespace="Microsoft.SharePoint" %&gt;<br />
</code></p>
<p>It really is this simple. You only need the Microsoft.SharePoint library! Now I begin my &lt;script&gt; section that contains the OnLoad override and other methods needed.</p>
<p><code> protected SPSite site;<br />
protected SPWeb web;</code></p>
<p><code> protected override void OnLoad(EventArgs e)<br />
{<br />
try<br />
{<br />
site = new SPSite("http://server/");<br />
web = site.OpenWeb("webdev");<br />
SPGroup grpMembers = web.SiteGroups["Website Development Members"];<br />
if(grpMembers.ContainsCurrentUser)<br />
{<br />
mvViewManager.ActiveViewIndex = 1;<br />
runPage();<br />
}<br />
}<br />
catch (Exception err)<br />
{<br />
litError.Visible = true;<br />
litError.Text = err.Message;<br />
}<br />
}</code></p>
<p>The first thing I do is just a simple check to make sure that the user is in a group that is allowed to perform these tasks. I also wrap the entire thing in a try/catch just to make my life easier for error catching and display it in a Literal that is in the content of the page (you&#8217;ll see later). I have been playing around with ViewManagers lately, and that controls what content is displayed on the page. The content of the page is as follows.</p>
<p><code>&lt;asp:Content ContentPlaceHolderID="PlaceHolderPageTitle" runat="server"&gt;<br />
Web Development Admin Tools - User Audit<br />
&lt;/asp:Content&gt;<br />
&lt;asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server"&gt;<br />
&lt;asp:MultiView ID="mvViewManager" runat="server" ActiveViewIndex="0"&gt;<br />
&lt;asp:View ID="vNoPerms" runat="server"&gt;<br />
&lt;h3&gt;You do not have permissions to view this page!&lt;/h3&gt;<br />
&lt;/asp:View&gt;<br />
&lt;asp:View ID="vMain" runat="server"&gt;<br />
&lt;asp:DropDownList ID="ddlUsers" runat="server" AutoPostBack="true" OnSelectedIndexChanged="findUser"/&gt;<br />
&lt;br/&gt;<br />
&lt;br/&gt;<br />
&lt;asp:Literal ID="litOutput" runat="server"/&gt;<br />
&lt;/asp:View&gt;<br />
&lt;/asp:MultiView&gt;<br />
&lt;asp:Literal ID="litError" runat="server" Visible="false"/&gt;<br />
&lt;/asp:Content&gt;</code></p>
<p>The litOutput and litError Literals are used for displaying information. The ddlUsers DropDownList is used for displaying a list of all users. As mentioned before, there are two views inside the ViewManager mvViewManager. The first view (index 0) is the default view. If the group evaluation from OnLoad passes, the second view (index 1) is displayed. The rest of this section is pretty self explanatory. Onto the methods that manipulate the DropDownList and output Literal.</p>
<p><code> protected void runPage()<br />
{<br />
if(Request.HttpMethod != "POST")<br />
{<br />
populateUsers();<br />
}<br />
}</code></p>
<p>protected void populateUsers()<br />
{<br />
foreach(SPUser user in site.RootWeb.AllUsers)<br />
{<br />
ListItem li = new ListItem();<br />
li.Text = user.Name;<br />
li.Value = user.ID.ToString();<br />
ddlUsers.Items.Add(li);<br />
}<br />
}</p>
<p>protected void findUser(Object sender, EventArgs e)<br />
{<br />
SPUser user = site.RootWeb.AllUsers.GetByID(Convert.ToInt32(ddlUsers.SelectedItem.Value));<br />
litOutput.Text = &#8220;&#8221;" + user.Name + &#8220;&#8221; belongs to the following groups:&lt;br/&gt;&lt;br/&gt;&#8221;;<br />
litOutput.Text += userMembership(user, site.RootWeb, &#8220;&#8221;);<br />
}</p>
<p>protected string userMembership(SPUser user, SPWeb web, string webPath)<br />
{<br />
StringBuilder retVal = new StringBuilder();<br />
//retVal.AppendFormat(&#8220;{0}&lt;br/&gt;&#8221;, web.Name == &#8220;&#8221; ? &#8220;Root&#8221; : web.Name);<br />
webPath += (webPath == &#8220;&#8221; ? &#8220;&#8221; : &#8220;/&#8221;) + web.Name;<br />
foreach(SPGroup group in web.Groups)<br />
{<br />
try<br />
{<br />
group.Users.GetByID(user.ID);<br />
retVal.AppendFormat(&#8220;&lt;b&gt;{0}&lt;/b&gt; &#8211; &lt;a href=&#8221;removeUserFromGroup.aspx?uid={1}&amp;gid={2}&amp;web={3}&#8221;&gt;REMOVE&lt;/a&gt;&lt;br/&gt;&#8221;,<br />
group.Name,<br />
user.ID,<br />
group.ID,<br />
webPath);<br />
}<br />
catch { }<br />
}<br />
foreach(SPWeb subweb in web.Webs)<br />
{<br />
retVal.AppendFormat(&#8220;{0}&#8221;, userMembership(user, subweb, webPath));<br />
}<br />
return retVal.ToString();<br />
}<br />
&lt;/script&gt;</p>
<p>The methods we have here are: runPage(), populateUsers(), findUser(Object sender, EventArgs e), and userMembership(SPUser user, SPWeb web, string webPath). I try to keep my methods in the order in which they are called. This makes my life easier when debugging, etc. The first method called is runPage(). It is just a simple starter that does a check to make sure that the page was not a POST. If it was a POST, then someone must have accessed the page incorrectly or someone chose a user from the DropDownList. This avoids duplicate population of the user DropDownList, and improper access of the page. That takes us to populateUsers(). This method uses SPWeb.AllUsers to find all the users in the system and add them to the DropDownList. It makes their name the Text property and their userID the Value property. Notice the DropDownList gets the AutoPostBack property set to True and the OnSelectedIndexChanged property set to the findUser() method. This writes some header information to the output Literal, then kicks off a call to the recursive method userMembership. Using the SPUser object of the user chosen, SPWeb object of the current web in which we&#8217;re looking, and a string of the path to the web in which we&#8217;re looking, userMembership will traverse all webs and sub-webs in the site collection looking for memberships to groups in those webs. It will return a string that provides the group name and a link to remove that person from the group.</p>
<h2>Removing Users from a Group</h2>
<p>This page is fairly simple. It takes three GET parameters to determine which SPWeb to use, the SPUser.ID, and SPGroup.ID. The same permissions logic is used to determine if the person should be viewing this page. Then, it also makes sure that the Request.HttpMethod is set to GET. The runPage() method then takes these parameters and removes the user from that group. The code is below the userAudit.aspx code provided.</p>
<h2>userAudit.aspx</h2>
<p><code>&lt;%@ Page language="C#"<br />
MasterPageFile="~masterurl/default.master"<br />
SmartNavigation="true"<br />
Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage,<br />
Microsoft.SharePoint,<br />
Version=12.0.0.0,<br />
Culture=neutral,<br />
PublicKeyToken=71e9bce111e9429c"<br />
meta:progid="SharePoint.WebPartPage.Document" %&gt;<br />
&lt;%@ Import Namespace="Microsoft.SharePoint" %&gt;<br />
&lt;script type="text/c#" runat="server"&gt;<br />
protected SPSite site;<br />
protected SPWeb web;<br />
protected override void OnLoad(EventArgs e)<br />
{<br />
try<br />
{<br />
site = new SPSite("http://server/");<br />
web = site.OpenWeb("webdev");<br />
SPGroup grpMembers = web.SiteGroups["Website Development Members"];<br />
if(grpMembers.ContainsCurrentUser)<br />
{<br />
mvViewManager.ActiveViewIndex = 1;<br />
runPage();<br />
}<br />
}<br />
catch (Exception err)<br />
{<br />
litError.Visible = true;<br />
litError.Text = err.Message;<br />
}<br />
}</code></p>
<p>protected void runPage()<br />
{<br />
if(Request.HttpMethod != &#8220;POST&#8221;)<br />
{<br />
populateUsers();<br />
}<br />
}</p>
<p>protected void populateUsers()<br />
{<br />
foreach(SPUser user in site.RootWeb.AllUsers)<br />
{<br />
ListItem li = new ListItem();<br />
li.Text = user.Name;<br />
li.Value = user.ID.ToString();<br />
ddlUsers.Items.Add(li);<br />
}<br />
}</p>
<p>protected void findUser(Object sender, EventArgs e)<br />
{<br />
SPUser user = site.RootWeb.AllUsers.GetByID(Convert.ToInt32(ddlUsers.SelectedItem.Value));<br />
litOutput.Text = &#8220;&#8221;" + user.Name + &#8220;&#8221; belongs to the following groups:&lt;br/&gt;&lt;br/&gt;&#8221;;<br />
litOutput.Text += userMembership(user, site.RootWeb, &#8220;&#8221;);<br />
}</p>
<p>protected string userMembership(SPUser user, SPWeb web, string webPath)<br />
{<br />
StringBuilder retVal = new StringBuilder();<br />
//retVal.AppendFormat(&#8220;{0}&lt;br/&gt;&#8221;, web.Name == &#8220;&#8221; ? &#8220;Root&#8221; : web.Name);<br />
webPath += (webPath == &#8220;&#8221; ? &#8220;&#8221; : &#8220;/&#8221;) + web.Name;<br />
foreach(SPGroup group in web.Groups)<br />
{<br />
try<br />
{<br />
group.Users.GetByID(user.ID);<br />
retVal.AppendFormat(&#8220;&lt;b&gt;{0}&lt;/b&gt; &#8211; &lt;a href=&#8221;removeUserFromGroup.aspx?uid={1}&amp;gid={2}&amp;web={3}&#8221;&gt;REMOVE&lt;/a&gt;&lt;br/&gt;&#8221;,<br />
group.Name,<br />
user.ID,<br />
group.ID,<br />
webPath);<br />
}<br />
catch { }<br />
}<br />
foreach(SPWeb subweb in web.Webs)<br />
{<br />
retVal.AppendFormat(&#8220;{0}&#8221;, userMembership(user, subweb, webPath));<br />
}<br />
return retVal.ToString();<br />
}<br />
&lt;/script&gt;<br />
&lt;asp:Content ContentPlaceHolderID=&#8221;PlaceHolderPageTitle&#8221; runat=&#8221;server&#8221;&gt;<br />
Web Development Admin Tools &#8211; User Audit<br />
&lt;/asp:Content&gt;<br />
&lt;asp:Content ContentPlaceHolderID=&#8221;PlaceHolderMain&#8221; runat=&#8221;server&#8221;&gt;<br />
&lt;asp:MultiView ID=&#8221;mvViewManager&#8221; runat=&#8221;server&#8221; ActiveViewIndex=&#8221;0&#8243;&gt;<br />
&lt;asp:View ID=&#8221;vNoPerms&#8221; runat=&#8221;server&#8221;&gt;<br />
&lt;h3&gt;You do not have permissions to view this page!&lt;/h3&gt;<br />
&lt;/asp:View&gt;<br />
&lt;asp:View ID=&#8221;vMain&#8221; runat=&#8221;server&#8221;&gt;<br />
&lt;asp:DropDownList ID=&#8221;ddlUsers&#8221; runat=&#8221;server&#8221; AutoPostBack=&#8221;true&#8221; OnSelectedIndexChanged=&#8221;findUser&#8221;/&gt;<br />
&lt;br/&gt;<br />
&lt;br/&gt;<br />
&lt;asp:Literal ID=&#8221;litOutput&#8221; runat=&#8221;server&#8221;/&gt;<br />
&lt;/asp:View&gt;<br />
&lt;/asp:MultiView&gt;<br />
&lt;asp:Literal ID=&#8221;litError&#8221; runat=&#8221;server&#8221; Visible=&#8221;false&#8221;/&gt;<br />
&lt;/asp:Content&gt;</p>
<h2>removeUserFromGroup.aspx</h2>
<p><code>&lt;%@ Page language="C#"<br />
MasterPageFile="~masterurl/default.master"<br />
SmartNavigation="true"<br />
Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage,<br />
Microsoft.SharePoint,<br />
Version=12.0.0.0,<br />
Culture=neutral,<br />
PublicKeyToken=71e9bce111e9429c"<br />
meta:progid="SharePoint.WebPartPage.Document" %&gt;<br />
&lt;%@ Import Namespace="Microsoft.SharePoint" %&gt;<br />
&lt;script type="text/c#" runat="server"&gt;<br />
protected SPSite site;<br />
protected SPWeb web;<br />
protected override void OnLoad(EventArgs e)<br />
{<br />
try<br />
{<br />
site = new SPSite("http://server/");<br />
web = site.OpenWeb("webdev");<br />
SPGroup grpMembers = web.SiteGroups["Website Development Members"];<br />
if(grpMembers.ContainsCurrentUser &amp;&amp; Request.HttpMethod == "GET")<br />
{<br />
mvViewManager.ActiveViewIndex = 1;<br />
runPage();<br />
}<br />
}<br />
catch (Exception err)<br />
{<br />
litError.Visible = true;<br />
litError.Text = err.Message;<br />
}<br />
}<br />
protected void runPage()<br />
{<br />
int uid = Convert.ToInt32(Request.QueryString["uid"]);<br />
int gid = Convert.ToInt32(Request.QueryString["gid"]);<br />
litOutput.Text += uid + " " + gid;<br />
SPWeb thisWeb = site.OpenWeb(Request.QueryString["web"]);<br />
SPGroup group = thisWeb.Groups.GetByID(gid);<br />
SPUser user = site.RootWeb.AllUsers.GetByID(uid);<br />
thisWeb.AllowUnsafeUpdates = true;<br />
thisWeb.Update();<br />
group.Users.RemoveByID(uid);<br />
thisWeb.AllowUnsafeUpdates = false;<br />
thisWeb.Update();<br />
litOutput.Text = """ + user.Name + "" REMOVED FROM: " + group.Name;<br />
}<br />
&lt;/script&gt;<br />
&lt;asp:Content ContentPlaceHolderID="PlaceHolderPageTitle" runat="server"&gt;<br />
Web Development Admin Tools - Remove User From Group<br />
&lt;/asp:Content&gt;<br />
&lt;asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server"&gt;<br />
&lt;asp:MultiView ID="mvViewManager" runat="server" ActiveViewIndex="0"&gt;<br />
&lt;asp:View ID="vNoPerms" runat="server"&gt;<br />
&lt;h3&gt;You do not have permissions to view this page!&lt;/h3&gt;<br />
&lt;/asp:View&gt;<br />
&lt;asp:View ID="vMain" runat="server"&gt;<br />
&lt;asp:Literal ID="litOutput" runat="server"/&gt;<br />
&lt;/asp:View&gt;<br />
&lt;/asp:MultiView&gt;<br />
&lt;asp:Literal ID="litError" runat="server" Visible="false"/&gt;<br />
&lt;/asp:Content&gt;</code></p>
<p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Ffugertech.com%2F2008%2F11%2F21%2Fsharepoint-user-auditing%2F&amp;title=SharePoint%3A%20User%20Auditing" id="wpa2a_14">Share/Save</a></p>]]></content:encoded>
			<wfw:commentRss>http://fugertech.com/2008/11/21/sharepoint-user-auditing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Dr. TransLog: Or How I Learned to Stop Worrying and Love SQL Server</title>
		<link>http://fugertech.com/2008/09/24/dr-translog-or-how-i-learned-to-stop-worrying-and-love-sql-server/</link>
		<comments>http://fugertech.com/2008/09/24/dr-translog-or-how-i-learned-to-stop-worrying-and-love-sql-server/#comments</comments>
		<pubDate>Wed, 24 Sep 2008 16:15:20 +0000</pubDate>
		<dc:creator>Tony</dc:creator>
				<category><![CDATA[Database Administration]]></category>
		<category><![CDATA[SharePoint]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Database]]></category>
		<category><![CDATA[DBA]]></category>
		<category><![CDATA[LDF]]></category>
		<category><![CDATA[LinkedIn]]></category>
		<category><![CDATA[MDF]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Microsoft SQL Server 2005]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[Transaction Log]]></category>
		<category><![CDATA[WSS 3.0]]></category>

		<guid isPermaLink="false">http://www.fugertech.com/?p=50</guid>
		<description><![CDATA[Preface My current day job is developing SharePoint-based web applications. On Thursday, September 11 (coincidence), the SharePoint site became unresponsive. It was not storing any new data, but viewing existing data was still possible. This was important as I have end-users that rely on reports stored in Document Libraries. The daily reports would not save <a href='http://fugertech.com/2008/09/24/dr-translog-or-how-i-learned-to-stop-worrying-and-love-sql-server/' class='excerpt-more'>[...]</a>]]></description>
			<content:encoded><![CDATA[<h2>Preface</h2>
<p>My current day job is developing SharePoint-based web applications. On Thursday, September 11 (coincidence), the SharePoint site became unresponsive. It was not storing any new data, but viewing existing data was still possible. This was important as I have end-users that rely on reports stored in Document Libraries. The daily reports would not save onto the website.</p>
<p>I&#8217;m sure that this article is SQL Server Administration 101 for most folks. For those diving into SharePoint with no DBA, this could be a lifesaver.</p>
<h2>Table of Contents</h2>
<p>Chapter 1: Transaction Log Deletion &#8211; what happens when I lost the WSS_Content transaction log<br />
Chapter 2: The Recovery &#8211; the steps to recovering the corrupt MDF file<br />
Chapter 3: Prevention &#8211; best practices for database and transaction log backup and truncation</p>
<p><span id="more-50"></span></p>
<h2>Chapter 1: Transaction Log Deletion</h2>
<p>After crawling around the servers a bit, I found out it was a disk space issue. Ends up that the Data drive for the SQL server that stores my SharePoint databases was full. I noticed a 136GB transaction log (LDF) file. I have not had formal SQL server training and the bulk of my experience is in the Linux world, so I was unsure of my next steps. Googling around I found people with similar issues and learned about sp_detach_db. I began to scramble as 8am was near and people would be coming into work. The detach was not working quickly enough. I shut down my SQL server processes and deleted the LDF file to start over on the transaction log. Little did I know that I just entered myself into a world of pain.</p>
<p>When the SQL server was brought back online, to my surprise, the WSS_Content database was toast. It would not allow access to it, and said that the database was corrupt. It was not shutdown cleanly&#8230; awesome news&#8230; lesson learned&#8230;</p>
<p>So, my first reaction is to call IT and ask for a restore of that file. More awesome news: &#8220;yea, the backups have been saying they were successful, but that file hasn&#8217;t been backed up&#8230;&#8221; Now I am worrying! I read further on Google results. I see forum replies saying things like, &#8220;well chances are if you lost your LDF file and the MDF won&#8217;t attach, the data is corrupt and you cannot get it back.&#8221;</p>
<p>I spend most of the day trying to find the right solution. Many of the answers on Google got me close, but did not work in the end. I find a piece of software called Stellar Phoenix SQL Database Recovery for $420. I think, well, I&#8217;ll try it and if it doesn&#8217;t work, I&#8217;ll get a refund. First problem I run into is the purchasing of the software. It takes 4 hours and 5 phone calls to get the software installed and the license working. They have a sales website that knows nothing about the software and their support team had me waiting on E-mails that never showed up. Again, lesson learned&#8230;</p>
<p>So, I finally have the software in hand. I rescue the MDF file, but to no avail. It transformed my 25GB MDF file into a 7MB MDF file. I apply for a refund (which is another story altogether). Soon, I learned to stop worrying and love SQL Server.</p>
<h2>Chapter 2: The Recovery</h2>
<p>At this point it is getting late in the evening and I am not getting anywhere. My brain is becoming mush, so I call it a night. The next morning I network around the office to learn of a colleague that is a SQL Server expert. She walks me through these steps and to my avail, the database comes back to life and SharePoint is happy. The icing on the cake is that I lost no data!</p>
<ol>
<li>Make a copy of the MDF file to a remote location just in case.</li>
<li>Rename the MDF file on the SQL server.</li>
<li>Run: <code>CREATE DATABASE WSS_Content</code> (Double check that it puts the MDF and LDF files in the same location as they were before. Add the proper options if need be.)</li>
<li>Shutdown the SQL server.</li>
<li>Copy original database MDF file over the newly created MDF file.</li>
<li>Rename, Move or Delete the newly create LDF file to &#8220;hide&#8221; it from SQL.</li>
<li>Start SQL server.</li>
<li>Run: <code>EXEC('ALTER DATABASE ''[WSS_Content]'' SET EMERGENCY');</code></li>
<li>Run: <code>ALTER DATABASE [WSS_Content] SET SINGLE_USER;</code></li>
<li>Run: <code>DBCC CHECKDB ([WSS_Content], REPAIR_ALLOW_DATA_LOSS) WITH NO_INFOMSGS;</code></li>
<li>Run: <code>ALTER DATABASE [WSS_Content] SET MULTI_USER;</code></li>
</ol>
<h2>Chapter 3: Prevention</h2>
<p>At this point, my transaction log would still grow to enormous proportions without the proper practices. My colleague and SQL Server expert showed me how to run backups and truncate the transaction logs daily.</p>
<ul>
<li>Run full database backups that overwrite themselves the next day.
<ul>
<li>Create a job for running full backups as so for each database that needs to be backed up:<br />
<code>BACKUP DATABASE [WSS_Content] TO DISK='C:PATHTOBACKUP[WSS_Content].bak' WITH INIT</code></li>
<li>If the database needs to have the transaction log truncated, run this after the BACKUP DATABASE command:<br />
<code>BACKUP LOG [WSS_Content] TO DISK='C:PATHTOBACKUP[WSS_Content]_LOG.bak'</code></li>
<li>Schedule the job to run once nightly such that it will end prior to any tape backups.</li>
</ul>
</li>
<li>Optional, run transaction log backups every 4 hours to ensure that data from 4 hours ago can be recovered in case of a problem throughout the day.
<ul>
<li>For each database that needs to have the transaction log backed up and truncated run:<br />
<code>BACKUP LOG [WSS_Content] TO DISK='C:PATHTOBACKUP[WSS_Content]_LOG.bak'</code></li>
<li>Schedule the job to run every 4 hours and stop prior to the full database backup above.</li>
</ul>
</li>
</ul>
<p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Ffugertech.com%2F2008%2F09%2F24%2Fdr-translog-or-how-i-learned-to-stop-worrying-and-love-sql-server%2F&amp;title=Dr.%20TransLog%3A%20Or%20How%20I%20Learned%20to%20Stop%20Worrying%20and%20Love%20SQL%20Server" id="wpa2a_16">Share/Save</a></p>]]></content:encoded>
			<wfw:commentRss>http://fugertech.com/2008/09/24/dr-translog-or-how-i-learned-to-stop-worrying-and-love-sql-server/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

