<?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>The Open Code Project</title>
	<atom:link href="http://opencodeproject.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://opencodeproject.com</link>
	<description>A place for informative articles about programming and technology.</description>
	<lastBuildDate>Sun, 19 Feb 2012 08:23:43 +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>Saving a Failing Hard Drive with GNU/Linux</title>
		<link>http://opencodeproject.com/2012/02/19/saving-a-failing-hard-drive-with-gnulinux/</link>
		<comments>http://opencodeproject.com/2012/02/19/saving-a-failing-hard-drive-with-gnulinux/#comments</comments>
		<pubDate>Sun, 19 Feb 2012 08:18:54 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[GNU/Linux]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[chkdsk]]></category>
		<category><![CDATA[Clonezilla]]></category>
		<category><![CDATA[dd]]></category>
		<category><![CDATA[ddrescue]]></category>
		<category><![CDATA[failing]]></category>
		<category><![CDATA[hard drive]]></category>
		<category><![CDATA[LibreOffice]]></category>
		<category><![CDATA[Office]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=201</guid>
		<description><![CDATA[I came to find out that my mother&#8217;s computer&#8217;s hard drive was failing. Programs took a long time to open and the computer would suddenly reboot for no reason. I discovered that the hard drive had bad sectors, which means &#8230; <a href="http://opencodeproject.com/2012/02/19/saving-a-failing-hard-drive-with-gnulinux/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I came to find out that my mother&#8217;s computer&#8217;s hard drive was failing. Programs took a long time to open and the computer would suddenly reboot for no reason. I discovered that the hard drive had bad sectors, which means potentially lost data if it decided to quit before I could save the data onto a different drive. Various GNU/Linux-based systems saved the day.</p>
<span id="more-201"></span>
<p>I determined that the problem must have been a failing hard drive, although there was very little to go by, so I purchased a new one and began the copy process. I started with <a href="http://clonezilla.org/">Clonezilla</a>, which is a very good drive duplication utility, but it kept failing. At first I thought this was because of the bad sectors, but it may have been because the USB drive that I had connected would turn off after a while (perhaps it wasn&#8217;t getting enough power from the one USB port). I decided to try something different.</p>

<p>Within Linux there&#8217;s a program called &#8216;<a href="http://en.wikipedia.org/wiki/Dd_(Unix)">dd</a>&#8216;. This program can take a bit-for-bit backup of a drive and copy it to another. Since each bit on the drive is duplicated, it will also copy partition maps and boot sectors. Surprisingly, it also copies potentially bad data from within a bad sector, but this is easily fixed afterward. The problem again with dd is that if the external backup drive keeps disconnecting from the computer then I would always have to restart it. This is where a program called <a href="http://www.gnu.org/software/ddrescue/ddrescue.html">ddrescue</a> saves the day</p>

<p>ddrescue takes the power of dd and gives it a memory. In the simplest configuration you would tell it what drive to copy from, what drive to copy data to, and where a log file can be found. If anything happens during the copy process, such as if a drive suddenly fails or stops responding, then ddrescue can continue where it left off because of the log file.</p>

<p>I was able to use the following command:</p>
<pre>
ddrescue -B -v /dev/sda /dev/sdb /mnt/log/ddrescue_logfile_AAAA-MM-JJ.log
</pre>
<p>The &#8220;-B&#8221; option will display units of 1024 instead of 1000 for the number of bits copied. The &#8220;-v&#8221; option is for verbose mode. &#8220;/dev/sda&#8221; is the source drive that has the errors. &#8220;/dev/sdb&#8221; is the new destination drive, it can be a larger size with Windows Vista or later computers. The &#8220;mnt/log&#8221; location is a mounted folder that is not located on either drive. This can be a flash drive or, in my case, a network share. The log file is named &#8220;ddrescue_logfile_2012-02-19.log&#8221;. As long as this same command is used whenever the copy process is interrupted, the process will automatically continue where it left off, as if nothing ever happened.</p>

<p>Once ddrescue was complete (a long time later), I put the new drive in the computer and booted into Windows. Windows booted up and I was able to log in. I told it to do a chkdsk by opening the command prompt and typing &#8220;chkdsk C: -F -R -B&#8221; and rebooted. Windows found several bad files and fixed them during the reboot. Using Windows&#8217; disk management tool I was able to extend the volume to fill the new, larger drive.</p>

<p>Later on I found out that Microsoft Office was unable to run due to the bad sectors being where an important Office file was located. I was able to use the Programs and Features Control Panel application to repair the Office install. I didn&#8217;t even need the Office disk (which you never get with new computers). Now everything works again. I do have to say that <a href="http://www.libreoffice.org/">LibreOffice</a> was used to supplement MS Office until I could find out the problem with MS Office and figure out a solution.</p>

<p>With this I have to thank the GNU/Linux developers, the creators of Clonezilla, dd, and ddrescue, <a href="http://www.ubuntu.com/">Ubuntu</a> for their live CDs, and the LibreOffice developers.</p>]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/02/19/saving-a-failing-hard-drive-with-gnulinux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to Set Up an Ubuntu Active Directory Client</title>
		<link>http://opencodeproject.com/2012/02/13/how-to-set-up-an-ubuntu-active-directory-client/</link>
		<comments>http://opencodeproject.com/2012/02/13/how-to-set-up-an-ubuntu-active-directory-client/#comments</comments>
		<pubDate>Mon, 13 Feb 2012 23:19:49 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[Active Directory]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Operating Systems]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[AD]]></category>
		<category><![CDATA[Likewise]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=186</guid>
		<description><![CDATA[Here I will explain a solution that worked for me to configure an Ubuntu workstation to authenticate with an Active Directory domain. The workstation was able to browse domain resources (ie, filesystem) without having to log in and it pulled &#8230; <a href="http://opencodeproject.com/2012/02/13/how-to-set-up-an-ubuntu-active-directory-client/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Here I will explain a solution that worked for me to configure an Ubuntu workstation to authenticate with an Active Directory domain. The workstation was able to browse domain resources (ie, filesystem) without having to log in and it pulled the AD group information from the domain controller. A process was used to map certain groups to Linux (Ubuntu) groups, however extensive verification of the results could not be performed.</p>
<span id="more-186"></span>
<p>
If you&#8217;re using a VMWare client then you must set a static MAC address, don&#8217;t allow your VM software to automatically update the MAC address.</p>
<h1>Setup your network</h1><p>
First, set your IP to static IP and configure DNS to point to your domain controllers or any other DNS server you may have. This will allow you to call Windows computers by their short names. You must also fill in the Search Domains with your domain name.</p>
<img width="425" height="515" alt="Network Settings" src="/wp-content/uploads/2012/02/Screenshot-Editing-Auto-eth0.png" /><p>
Notice that I used &#8220;mydomain.<strong>local</strong>&#8220;. This causes a stupid issue with mDNS that will need to be adjusted. If you use something like &#8220;mydomain.com&#8221; then you shouldn&#8217;t need to do the next step.</p>
<h1>Modify nsswitch.conf (fix the .local mDNS issue)</h1>
<p>
Open the file <strong>/etc/nsswitch.conf</strong> by starting your terminal (Applications &gt;&gt; Accessories &gt;&gt; Terminal) and type in:</p>
<pre class="prettyprint">
sudo gedit /etc/nsswitch.conf</pre>
<p>
Modify the line that reads:</p>
<pre class="prettyprint">
hosts:          files mdns4_minimal [NOTFOUND=return] dns mdns4</pre>
<p>
Change it to:</p>
<pre class="prettyprint">
hosts:          files dns mdns4_minimal mdns4</pre>
<p>
This should allow a program called <a href="http://www.likewise.com/products/likewise_open/">Likewise-open</a> to authenticate with Active Directory and create a computer account.</p>
<h1>Install Likewise-open</h1>
<p>
Now, in the terminal, type in</p>
<pre class="prettyprint">
sudo apt-get install likewise-open5 likewise-open5-gui</pre>
<p>
You can also use Synaptic and search for &#8220;likewise&#8221;. You&#8217;ll notice a likewise-open and also likewise-open5. They both seem to work. I have installed likewise-open5 and likewise-open5-gui.</p><p>
<h1><strong>Join the domain</strong></h1></p><p>
Once those are installed you can configure Likewise-open by going to System &gt;&gt; Administration &gt;&gt; Active Directory membership. Likewise-open will ask you for a username and password. This user must have privileges to join a computer to the domain.</p><p>
<img width="414" height="551" alt="Likewise-open GUI configuration" src="/wp-content/uploads/2012/02/Screenshot-Ubuntu-32bit-Running-VirtualBox-OSE.png" /></p><p>
You can also use the command line to join the computer to the domain:</p>
<pre class="prettyprint">
sudo domainjoin-cli join mydomain.local Administrator</pre>
<p>
You will then be asked to restart the computer. When the computer restarts you can use the Other User login option.</p>
<img width="500" height="374" src="/wp-content/uploads/2012/02/4065116840_93b8fb959b.jpg" alt="Other User login" /><p>
Type in &#8220;<strong>mydomain\username</strong>&#8221; where &#8220;mydomain&#8221; is the short name for your domain and &#8220;username&#8221; is some domain user account. If all of the steps above worked out well then you should be authenticated and logged into Ubuntu. You might get an Authentication Failure notice, which is usually due to one of the network settings from above being messed up or the username being typed in wrong.</p><p>
When you&#8217;ve verified that the account can log in you can log out and return to your normal Ubuntu account. A few more optional steps can be used to complete the process.</p><p>
<h1><strong>Sudoers</strong></h1></p><p>
Edit your sudoers file by opening up a terminal window and typing </p>
<pre class="prettyprint">
sudo visudo</pre>
<p>
A vi-style program will show and allow you to edit the sudoers configuration. Under the %admin line you should add the following:</p>
<pre class="prettyprint">
%MYDOMAIN\\Domain^Admins ALL=(ALL) ALL</pre>
<p>
Make MYDOMAIN whatever your short domain name is (don&#8217;t make it MYDOMAIN.LOCAL).</p>
<h1>Add users to the login screen</h1>

<p>Most domains won&#8217;t want this but you might like it for a Kiosk or a sample computer. When using domain logins you will have to type &#8220;mydomain\username&#8221; using the Other User login option. This can be too many steps for some people so it may be necessary to add a single-click option for their username. The end result will look like the following image.</p>
<img width="500" height="362" src="/wp-content/uploads/2012/02/Screenshot-Ubuntu-32bit-Running-VirtualBox-OSE-1.png" alt="Ubuntu login" />
<p>
Log in as your domain user and open up the terminal. Type &#8220;id&#8221; in the terminal window to view your UID and GID information. It will look something like:</p>
<pre class="prettyprint">
DOMAIN\username@ubuntu-client:~$ id
uid=1234567889(username) ....</pre>

<p>We only really care about the uid at this point. Write it down and log out of this user and back into the normal Ubuntu user account.</p><p>
You will need to edit your <strong>/etc/passwd</strong> file. Open a terminal window and type the following command:</p>
<pre class="prettyprint">
sudo gedit /etc/passwd</pre>

<p>Make a new line at the bottom and duplicate the following information with the numbers that you wrote down: </p>
<pre class="prettyprint">
DOMAIN\username:x:1234567889:0:John Doe,,,:/home/DOMAIN/username:/bin/bash</pre>

<p>The group id (0) is admin to help make this person a local admin, but you should be able to use the uid in it&#8217;s place if you don&#8217;t want to use the admin group. It should look like this in that case:</p>
<pre class="prettyprint">
DOMAIN\username:x:1234567889:1234567889:John Doe,,,:/home/DOMAIN/username:/bin/bash</pre>

<p>Now you can log out of the local Ubuntu user&#8217;s account and see the updated login screen. The domain user or kiosk user can click on the big button and type in the password for the account.</p>
]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/02/13/how-to-set-up-an-ubuntu-active-directory-client/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Getting password complexity requirements with VBScript and Powershell</title>
		<link>http://opencodeproject.com/2012/02/13/getting-password-complexity-requirements-with-vbscript-and-powershell/</link>
		<comments>http://opencodeproject.com/2012/02/13/getting-password-complexity-requirements-with-vbscript-and-powershell/#comments</comments>
		<pubDate>Mon, 13 Feb 2012 22:01:43 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[Active Directory]]></category>
		<category><![CDATA[Powershell]]></category>
		<category><![CDATA[UMRA]]></category>
		<category><![CDATA[VBScript]]></category>
		<category><![CDATA[Domain Policy]]></category>
		<category><![CDATA[Password Policy]]></category>
		<category><![CDATA[PS1]]></category>
		<category><![CDATA[Script]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=177</guid>
		<description><![CDATA[I was helping with a method to check a user&#8217;s password against the domain via a program called UMRA. The idea was to use basic JavaScript validation for some of the password checks, but then to send the validation to &#8230; <a href="http://opencodeproject.com/2012/02/13/getting-password-complexity-requirements-with-vbscript-and-powershell/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I was helping with a method to check a user&#8217;s password against the domain via a program called <a title="User Management Resource Administrator" href="http://www.tools4ever.com/products/user-management-resource-administrator/">UMRA</a>. The idea was to use basic JavaScript validation for some of the password checks, but then to send the validation to the server for true verification. There currently is no process for checking a password without first creating the account and checking the error code from Active Directory. I did some searching and found various different methods from Microsoft, but nothing that hit the nail on the head. Then doing one final Google search for &#8220;vbscript domain policy&#8221; lead me to <a href="http://www.cruto.com/resources/vbscript/vbscript-examples/ad/users/pwds/List-Domain-Password-Policy-Settings.asp">this website</a>.</p>
<span id="more-177"></span>
<p>This script provides the basic information for the default domain password policy.</p>

<pre class="prettyprint">
Const MIN_IN_DAY = 1440
Const SEC_IN_MIN = 60
 
Set objDomain = GetObject("WinNT://fabrikam")
Set objAdS = GetObject("LDAP://dc=fabrikam,dc=com")
 
intMaxPwdAgeSeconds = objDomain.Get("MaxPasswordAge")
intMinPwdAgeSeconds = objDomain.Get("MinPasswordAge")
intLockOutObservationWindowSeconds = objDomain.Get("LockoutObservationInterval")
intLockoutDurationSeconds = objDomain.Get("AutoUnlockInterval")
intMinPwdLength = objAds.Get("minPwdLength")
 
intPwdHistoryLength = objAds.Get("pwdHistoryLength")
intPwdProperties = objAds.Get("pwdProperties")
intLockoutThreshold = objAds.Get("lockoutThreshold")
intMaxPwdAgeDays = _
  ((intMaxPwdAgeSeconds/SEC_IN_MIN)/MIN_IN_DAY) &#038; " days"
intMinPwdAgeDays = _
  ((intMinPwdAgeSeconds/SEC_IN_MIN)/MIN_IN_DAY) &#038; " days"
intLockOutObservationWindowMinutes = _
  (intLockOutObservationWindowSeconds/SEC_IN_MIN) &#038; " minutes"
 
If intLockoutDurationSeconds &lt;&gt; -1 Then
  intLockoutDurationMinutes = _
(intLockOutDurationSeconds/SEC_IN_MIN) &#038; " minutes"
Else
  intLockoutDurationMinutes = _
    "Administrator must manually unlock locked accounts"
End If

Dim out: out = "" 
out = out &#038; "maxPwdAge = " &#038; intMaxPwdAgeDays &#038; vbCrLf
out = out &#038; "minPwdAge = " &#038; intMinPwdAgeDays &#038; vbCrLf 
out = out &#038; "minPwdLength = " &#038; intMinPwdLength &#038; vbCrLf
out = out &#038; "pwdHistoryLength = " &#038; intPwdHistoryLength &#038; vbCrLf
out = out &#038; "pwdProperties = " &#038; intPwdProperties &#038; vbCrLf
out = out &#038; "lockOutThreshold = " &#038; intLockoutThreshold &#038; vbCrLf
out = out &#038; "lockOutObservationWindow = " &#038; intLockOutObservationWindowMinutes &#038; vbCrLf
out = out &#038; "lockOutDuration = " &#038; intLockoutDurationMinutes &#038; vbCrLf
</pre>

<p>Note: The script above is copied from the linked website for backup purposes. I give full credit of the above script to the original &#8211; undocumented &#8211; author. The website claims no responsibility for damages, as neither do I, however the above script is only pulling information. The website also does not list a copyright, so until they contact me you can find this script here as well.</p>
<p>
Using the above script as a platform for what I wanted to do, I converted the code to Powershell and added some tests of my own to check if a supplied password is able to be reset on the user account and if the password matches the criteria. The script supplies either the domain password options, or a TRUE/FALSE value if the password works.</p>
<p>
Below is the full PowerShell script with simple usage instructions. You can save it to a file like &#8220;GPPassword.ps1&#8243; and execute it using powershell with &#8220;./GPPassword.ps1 somedomain.local [username] [testpassword]&#8220;. The username and testpassword are optional if you want to test a password for a user (eg. for password validation via the web).<br />
<pre class="prettyprint">
$MIN_IN_DAY = 1440;
$SEC_IN_MIN = 60;

#options for pwdProperties
$DOMAIN_PASSWORD_COMPLEX = 1; 
#The password must have a mix of at least two of the following types of characters:
#Uppercase characters
#Lowercase characters
#Numerals
$DOMAIN_PASSWORD_NO_ANON_CHANGE = 2; 
#The password cannot be changed without logging on. Otherwise, if your password has expired, you can change your password and then log on.
$DOMAIN_PASSWORD_NO_CLEAR_CHANGE = 4; 
#Forces the client to use a protocol that does not allow the domain controller to get the plaintext password.
$DOMAIN_LOCKOUT_ADMINS = 8;
#Allows the built-in administrator account to be locked out from network logons.
$DOMAIN_PASSWORD_STORE_CLEARTEXT = 16;
#The directory service is storing a plaintext password for all users instead of a hash function of the password.
$DOMAIN_REFUSE_PASSWORD_CHANGE = 32;
#Removes the requirement that the machine account password be automatically changed every week.
#This value should not be used as it can weaken security.

if($args.Length -gt 0){	
	#Gets the domain arguments
	$FQDN = $args[0];
	$objDomain = [ADSI]"WinNT://$($FQDN)";

	$SplitDomain = $FQDN.Split(".");
	$LDAPDomain = ""
	foreach ($dc in $SplitDomain) { $LDAPDomain += "dc=$($dc),"}
	
	if($LDAPDomain.substring($LDAPDomain.Length-1,1) -eq ","){ 
		$LDAPDomain = $LDAPDomain.substring(0,$LDAPDomain.Length-1); 
	}	


	$objAds = New-Object DirectoryServices.DirectoryEntry("LDAP://$($LDAPDomain)");

	$intMaxPwdAgeSeconds = $objDomain.MaxPasswordAge;
	$intMinPwdAgeSeconds = $objDomain.MinPasswordAge;
	$intLockOutObservationWindowSeconds = $objDomain.LockoutObservationInterval;
	$intLockoutDurationSeconds = $objDomain.AutoUnlockInterval;
	$intMinPwdLength = $objAds.minPwdLength.Value;

	$intPwdHistoryLength = $objAds.pwdHistoryLength.Value;
	$intPwdProperties = $objAds.pwdProperties;
	$intLockoutThreshold = $objAds.lockoutThreshold;

	$intMaxPwdAgeDays = (($intMaxPwdAgeSeconds.Value/$SEC_IN_MIN)/$MIN_IN_DAY).ToString() + " days";
	$intMinPwdAgeDays = (($intMinPwdAgeSeconds.Value/$SEC_IN_MIN)/$MIN_IN_DAY).ToString() + " days";
	$intLockOutObservationWindowMinutes = ($intLockOutObservationWindowSeconds.Value/$SEC_IN_MIN).ToString() + " minutes"; 
	if($intLockoutDurationSeconds -ne -1){
  		$intLockoutDurationMinutes = ($intLockOutDurationSeconds.Value/$SEC_IN_MIN).ToString() + " minutes";
	}else{
  		$intLockoutDurationMinutes = "Administrator must manually unlock locked accounts";
	}


	$domainPasswordOptions = New-Object System.Object
	$domainPasswordOptions |Add-Member -type NoteProperty -name maxPwdAge -value $intMaxPwdAgeDays
	$domainPasswordOptions |Add-Member -type NoteProperty -name minPwdAge -value $intMinPwdAgeDays
	$domainPasswordOptions |Add-Member -type NoteProperty -name minPwdLength -value $intMinPwdLength
	$domainPasswordOptions |Add-Member -type NoteProperty -name pwdHistoryLength -value $intPwdHistoryLength
	$domainPasswordOptions |Add-Member -type NoteProperty -name pwdProperties -value $intPwdProperties


	$domainPasswordOptions |Add-Member -type NoteProperty -name complexPassword -value ([int]($intPwdProperties.ToString()) -band $Domain_PASSWORD_COMPLEX)
	$domainPasswordOptions |Add-Member -type NoteProperty -name noAnonymousChange -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_PASSWORD_NO_ANON_CHANGE)
	$domainPasswordOptions |Add-Member -type NoteProperty -name noCleartextChange -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_PASSWORD_NO_CLEAR_CHANGE)
	$domainPasswordOptions |Add-Member -type NoteProperty -name lockoutAdmins -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_LOCKOUT_ADMINS)
	$domainPasswordOptions |Add-Member -type NoteProperty -name storeCleartext -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_PASSWORD_STORE_CLEARTEXT)
	$domainPasswordOptions |Add-Member -type NoteProperty -name refuseMachineWeeklyPwdChange -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_REFUSE_PASSWORD_CHANGE)

	$domainPasswordOptions |Add-Member -type NoteProperty -name lockOutThreshold -value $intLockoutThreshold
	$domainPasswordOptions |Add-Member -type NoteProperty -name lockOutObservationWindow -value $intLockOutObservationWindowMinutes
	$domainPasswordOptions |Add-Member -type NoteProperty -name lockOutDuration -value $intLockoutDurationMinutes

	if($args.Length -eq 1){	
		#####uncomment the following line to see a report in the logs.
		$domainPasswordOptions
	}elseif($args.Length -eq 3){
		$user = $args[1];
		$searcher = New-object System.DirectoryServices.DirectorySearcher $objAds;
		$searcher.filter = "(&#038;(objectCategory=person)(objectClass=user)(sAMAccountName=$($user)))";
		$user = $searcher.FindOne();
		$username = $user.Properties.Item("samaccountname");
		$userPwdLastSet = [datetime]::fromfiletime($user.Properties.Item("pwdLastSet")[0]);
		$CurrentDate = Get-Date
		$diff = $CurrentDate-$userPwdLastSet
		#$diff
		$passwordCheckResult = $TRUE;

		#check the password age
		if($domainPasswordOptions.minPwdAge -gt $diff.Days -and $passwordCheckResult){
			$passwordCheckResult = $FALSE;
		}

		#check the length
		if(([String]$args[2]).Length -lt $domainPasswordOptions.minPwdLength -and $passwordCheckResult){
			$passwordCheckResult = $FALSE;
		}

		#check if the password contains the username, or the username contains the password
		if((([String]$args[2]).ToLower().Contains(([String]$username).ToLower()) -or ([String]$username).ToLower().Contains(([String]$args[2]).ToLower())) -and $passwordCheckResult){
			$passwordCheckResult = $FALSE;
		}

		#check if the password complexity rule is enabled
		if($domainPasswordOptions.complexPassword -eq $DOMAIN_PASSWORD_COMPLEX -and $passwordCheckResult){
			#check password for complexity
			if($args[2].Length -ge 6){
				$count = 0;
				if($args[2] -match "[A-Z]+"){
					$count++;
				}
				if($args[2] -match "[a-z]+"){
					$count++;
				}
				if($args[2] -match "\\d+"){
					$count++;
				}
				if($args[2] -match "\\W+"){
					$count++;
				}
				if($args[2] -match "\\X+"){
					$count++;
				}
				if($count -ge 3){
					$passwordCheckResult = $TRUE;
				}else{
					$passwordCheckResult = $FALSE;
				}
			}else{
				$passwordCheckResult = $FALSE;
			}
		}
		echo $passwordCheckResult
		exit;
	}else{
		echo "Usage1: ./PasswordOptions.ps1 domain.local [SomeUsername] [testPassword]"
	}
}else{
	echo "Usage: ./PasswordOptions.ps1 domain.local [SomeUsername] [testPassword]"
}
</pre>
</p>]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/02/13/getting-password-complexity-requirements-with-vbscript-and-powershell/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Making DVDs from JVC Everio MOD files using Ubuntu, WinFF, Avidemux, and DeVeDe</title>
		<link>http://opencodeproject.com/2012/02/10/making-dvds-from-jvc-everio-mod-files-using-ubuntu-winff-avidemux-and-devede/</link>
		<comments>http://opencodeproject.com/2012/02/10/making-dvds-from-jvc-everio-mod-files-using-ubuntu-winff-avidemux-and-devede/#comments</comments>
		<pubDate>Fri, 10 Feb 2012 21:25:22 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[Bash]]></category>
		<category><![CDATA[Graphics and Video]]></category>
		<category><![CDATA[Avidemux]]></category>
		<category><![CDATA[DeVeDe]]></category>
		<category><![CDATA[DVD]]></category>
		<category><![CDATA[JVC Everio]]></category>
		<category><![CDATA[MOD Files]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[WinFF]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=166</guid>
		<description><![CDATA[I purchased the JVC Everio hard disk camcorder with the intention of quickly copying the movie files from it and burning them to a DVD. Since I use Ubuntu and since the camera automatically detected as a USB hard drive, &#8230; <a href="http://opencodeproject.com/2012/02/10/making-dvds-from-jvc-everio-mod-files-using-ubuntu-winff-avidemux-and-devede/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I purchased the JVC Everio hard disk camcorder with the intention of quickly copying the movie files from it and burning them to a DVD. Since I use Ubuntu and since the camera automatically detected as a USB hard drive, I did not install the supplied software, opting for open source applications instead.</p>
<span id="more-166"></span>
<p>
The JVC Everio is a nice little video camera which has good video quality and a deep digital zoom. The camera records the video to an internal hard drive offering up to 70 hours of recording, or about 7 at high quality (60GB model). This is interesting because the battery only lasts for 1 hour 20 minutes, but that&#8221;s easy to work with. </p>

<p>
The camera stores individual videos whenever the record button is pressed to record and then pressed again to stop. The next video file is created when the record button is pressed again. The files are stored in MOD format with MPEG-2 PS compression and Dolby Digital 2 channel audio.</p>
<p>
After connecting the camera to the computer with a USB cord, the MOD files must be copied off the camera. After copying the files from the camera you can disconnect it and erase the files (or erase them after everything is finished).</p>
<p>
Using WinFF (in Synaptic install WinFF , linux-restricted-modules and ubuntu-restricted-extras for MPEG -2 compatibility) press the Add button to select the MOD files you want to convert to MPG. In the Ouput Details section select &quot;DVD&quot;, or whatever works for you, then select NTSC DVD HQ (16:9) for the Device Preset. This will produce a file that is nearly 100% of the original quality. Choose an output folder that&#8221;s different from the folder where you MOD files are located (this can be a subfolder). </p>
<p>
For the additional options section, you only need to modify the Video Settings tab. Modify your video size to 720&#215;480 so that your TV can display it properly (you can also choose 1920&#215;1080 if you have the HD version). Set your aspect ratio at 16:9, unless you&#8221;ve set your camera to 4:3 using the camera menu system.</p>
<p>
Check 2 pass for better quality images during fast motion sequences (this slows down conversion but improves overall quality). Also check Deinterlace to make the video appear normal. You can deinterlace the video now or by using DeVeDe later.</p>
<p>
<a href="/wp-content/uploads/2012/02/winff-jvc-everio-settings.png"><img width="413" height="426" border="0" alt="WinFF settings for the JVC Everio" src="/wp-content/uploads/2012/02/winff-jvc-everio-settings.png" /></a></p><p>
Once these files have been converted to MPG files and placed in your new directory you can remove the MOD files from your computer. Alternatively, keep them for a while until you complete the process.</p>
<p>
Your converted folder will now list the movies as MOV001.mpg though MOVFFF.mpg. In order to quickly import these files to Avidemux so it can join them together, they must be in numerical order using the decimal system, not hexadecimal notation.</p>
<p>
The following script will rename all files in the folder that start with MOV and end with the .mpg extension. This will rename the files to the decimal equivalent so that they can be easily imported to Avidemux. Make sure that this script is contained within the converted video folder.
</p>
<pre class="prettyprint">
#!/bin/bash

for file in `ls *.mpg`
do
	 number=${file#*V}	 #last 3 should be &quot;000-fff&quot;
	 number=${number%.*}
	 number=`echo &quot;ibase=16;obase=A;${number}&quot; | bc`
	 echo &quot;Converting ${file} to MOV${number}.mpg&quot;
	 mv -f ${file} &quot;MOV${number}.mpg&quot;
done

#for renumbering the new files for easy
#avidemux processing
#the following numbers the files 1-n
#pardon the weird coding method, it''s a hack job
currNum=1
for file in `ls -v *.mpg`
do     
	 tempNum=$currNum
	if [ &quot;$tempNum&quot; -lt 10 ]; then
		tempNum=`echo 000${tempNum}`
	elif [ &quot;$tempNum&quot; -lt 100 ];	then
		tempNum=`echo 00${tempNum}`
	elif [ &quot;$tempNum&quot; -lt 1000 ];	then
		tempNum=`echo 0${tempNum}`
	fi
	echo &quot;Converting ${file} to MOV_${tempNum}.mpg&quot;    
	mv -f ${file} &quot;MOV_${tempNum}.mpg&quot;
	currNum=`echo $((currNum+1))`
done</pre>
<p>
Again, make sure this script is in the converted video folder. You can execute it by typing:<br />
 </p>
<pre class="prettyprint">
~/Videos/Video Camera/Converted$ sh EverioNamesHexToDec.sh</pre>
<p>
Now that the files are in numerical order using the decimal system, you can import them into Avidemux.</p>
<p>
Open Avidemux and click the Open button. Select the first file in your series (probably called MOV1.mpg now). A popup will ask you if you would like to index it. Press Yes for indexing. A new dialog will ask you to import the other files. Press Yes to import all files.</p>
<p>
Using Avidemux you can add some special effects and perform some video surgery, but I just want to export the merged video to a new MPEG file. If you really want to tweak the video compression then you can play around with the Video, Audio, and Format settings in the left-hand column. I chose to set the Video and Audio to Copy and AVI for the format. To save the video press File &gt; Save &gt; Save Video (Ctrl-s). Type in a filename, like &quot;MyVideo.mpg&quot; and press save.</p>
<p>

<a href="/wp-content/uploads/2012/02/avidemux-jvc-everio-settings.png"><img width="500" height="368" border="0" alt="Avidemux settings for the JVC Everio imported files" src="/wp-content/uploads/2012/02/avidemux-jvc-everio-settings.png" /></a></p>
<p>
Now that you have the final version of the merged MPEG you can write it to a disk. In order to do this you can use DeVeDe. When you open DeVeDe select the &quot;Video DVD&quot; option. This brings up a disk structure window. The following pictures illustrate how I would make my DVD file.</p>
<p>
Press the Add button on the Structure window.</p>
<p>
<a href="/wp-content/uploads/2012/02/DeVeDe-structure.png"><img width="468" height="447" border="0" alt="DeVeDe structure window" src="/wp-content/uploads/2012/02/DeVeDe-structure.png" /></a></p>
<p>
Select your video file and expand the Advanced options menu. Click the Video format tab and choose 16:9 for the correct aspect ratio.</p>
<p>
<a href="/wp-content/uploads/2012/02/DeVeDe-file-properties.png"><img width="456" height="437" border="0" alt="DeVeDe file properties" src="/wp-content/uploads/2012/02/DeVeDe-file-properties.png" /></a></p>
<p>
In the Video options tab select &quot;Scale picture&quot; to eliminate the black lines. This will allow HDTVs to show the video at full screen. This step is kind of optional since you&#8221;ve already selected the 16:9 aspect ratio, but it&#8221;s better to be safe.</p>
<p>
<a href="/wp-content/uploads/2012/02/DeVeDe-file-properties-video-options.png"><img width="456" height="226" border="0" alt="DeVeDe file properties video options" src="/wp-content/uploads/2012/02/DeVeDe-file-properties-video-options.png" /></a></p>
<p>
In the Audio tab select the &quot;Create DVD with 5.1 channel sound&quot; option. This will give your 2.1 dolby digital video the effect of having 5.1 channels. You can play around with this, but it seems to make the audio clear on TVs with and without sound systems.</p>
<p>
<a href="/wp-content/uploads/2012/02/DeVeDe-file-properties-audio.png"><img width="454" height="126" border="0" alt="DeVeDe file properties audio" src="/wp-content/uploads/2012/02/DeVeDe-file-properties-audio.png" /></a></p><p>
Press OK to save your options and return to the structure window.</p>
<p>
In your Titles section you can rename the title of the video. This title will be displayed if you choose to show the DVD menu. If you don&#8221;t want the DVD menu to display then click the &#8220;Menu options&#8221; button and select the &#8220;Jump to the first title at startup&#8221; option.</p>
<p>
In the Advanced options for the disc structure window, select &#8220;Create an ISO or BIN/CUE image, ready to burn to a disc&#8221;. Then click the Forward button. A new folder will be created with the name you choose in the next window. This is where some temporary files will be made and finally the ISO file to write to the disk.</p>
<p>
When the ISO file is created and DeVeDe is finished you can right-click on the ISO and select the Write to disk option.</p>]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/02/10/making-dvds-from-jvc-everio-mod-files-using-ubuntu-winff-avidemux-and-devede/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SQL LIKE vs Equals ( = ) Performance</title>
		<link>http://opencodeproject.com/2012/02/10/sql-like-vs-equals-performance/</link>
		<comments>http://opencodeproject.com/2012/02/10/sql-like-vs-equals-performance/#comments</comments>
		<pubDate>Fri, 10 Feb 2012 06:29:51 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[Equals]]></category>
		<category><![CDATA[LIKE]]></category>
		<category><![CDATA[MSSQL]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=159</guid>
		<description><![CDATA[While helping out a fellow developer I decided to try to determine which method of string searching was faster, LIKE or equals. I wanted to discover the basic result, with no indexes to speed things up, and no thoughts regarding &#8230; <a href="http://opencodeproject.com/2012/02/10/sql-like-vs-equals-performance/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>While helping out a fellow developer I decided to try to determine which method of string searching was faster, LIKE or equals. I wanted to discover the basic result, with no indexes to speed things up, and no thoughts regarding underlying database principals of LIO, disk, or memory access. Just a simple test of speed. We can go on all day testing LIO (logical IO) performance and other variables, but I think they would effectively come to a similar conclusion.</p>
<span id="more-159"></span>
<h1>The Table</h1>

<p>I&#8217;ve created a table with 1 million random strings that are 8 characters long in T-SQL. The table has a primary key column and an un-indexed varchar(50) column.</p>

<h1>The code</h1>
<pre class="prettyprint">
DECLARE @sttime datetime;
DECLARE @endtime datetime;
Set @sttime = getdate();
SELECT [ID]
      ,[Text]
  FROM [dbo].[Table_1]
  WHERE [Text] LIKE '1f17D39C%';
Set @endtime = GETDATE();

SELECT @sttime AS Time1, @endtime,
    DATEDIFF(millisecond,@sttime,@endtime) AS TimeMilli;
</pre>
<h1>Result of LIKE</h1

<p>Between 156 and 190ms to execute (ran multiple times)</p>

<h1>Test 2</h1>

<pre class="prettyprint">
SELECT [ID]
      ,[Text]
  FROM [dbo].[Table_1]
  WHERE [Text] = '1f17D39C';
  -- also tried '9a6fF5CE', row 590000
</pre>

<h1>Result of Equals</h1>

<p>Between 123 and 140ms (with multiple examples)</p>

<p>There you have it. The equals test is slightly faster with a million un-indexed rows. It would be useful to use if you were performing the same query a million times.</p>

<p>If we were to add indexes then both queries take an insignificant amount of time so we could say that they are both comparable and using either LIKE or equals has no effect on the database.</p>

<h1>Indexed column test</h1>

<p>
<strong>Equals Query Time:</strong> 0ms<br />
<strong>LIKE Query Time:</strong> 0ms
</p>

As I said before, this doesn&#8217;t take PIO or LIO into account, and this result may only be relative to very large databases (much more than a measly 1 million rows).  I implore anyone to expand on this topic if you believe LIO or any thing else is skewing these numbers.

<a href="http://www.simple-talk.com/sql/performance/simple-query-tuning-with-statistics-io-and-execution-plans/">This article</a> goes into a lot more detail regarding Statistics IO in T-SQL.]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/02/10/sql-like-vs-equals-performance/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Integrating Real-Time SQL Triggers to manage Active Directory Accounts with UMRA</title>
		<link>http://opencodeproject.com/2012/02/09/integrating-real-time-sql-triggers-to-manage-active-directory-accounts-with-umra-2/</link>
		<comments>http://opencodeproject.com/2012/02/09/integrating-real-time-sql-triggers-to-manage-active-directory-accounts-with-umra-2/#comments</comments>
		<pubDate>Thu, 09 Feb 2012 21:14:19 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[Active Directory]]></category>
		<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[UMRA]]></category>
		<category><![CDATA[AD]]></category>
		<category><![CDATA[COM Object]]></category>
		<category><![CDATA[sp_OACreate]]></category>
		<category><![CDATA[Trigger]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=141</guid>
		<description><![CDATA[Triggers allow a set of SQL code to run on several types of SQL actions, such as INSERT, UPDATE, or DELETE. For instance, upon an INSERT, the SQL engine will call the trigger configured for the INSERT action. The trigger &#8230; <a href="http://opencodeproject.com/2012/02/09/integrating-real-time-sql-triggers-to-manage-active-directory-accounts-with-umra-2/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>
Triggers allow a set of SQL code to run on several types of SQL actions, such as INSERT, UPDATE, or DELETE. For instance, upon an INSERT, the SQL engine will call the trigger configured for the INSERT action. The trigger specified will have programming logic to perform actions on other systems. With OLE Automation enabled in SQL, the trigger will have the ability to perform actions using COM objects.</p>
<span id="more-141"></span>
<p>
<a href="http://www.tools4ever.com/products/user-management-resource-administrator/">User Management Resource Administrator</a> (UMRA) provides a COM object for interaction with other applications. This COM object allows another system to set variables and execute projects which can manage Active Directory accounts, set up Exchange email, manage Lotus Notes, SAP, and update many multiple database systems like PowerSchool (Oracle) or Skyward. The COM object also allows the other application to pull information from UMRA, such as user information, AD information, or variables set up by the UMRA script developer.</p>

<h1>Benefits to using Triggers with UMRA</h1>
<p>
OLE Automation using triggers will allow SQL to execute a UMRA project in the same fashion that websites integrate with UMRA.</p>
<p>
In a website you can fill in information for a new user account and press the &ldquo;Submit&rdquo; button, which executes a UMRA project. With triggers, the help desk or HR staff can use any program they wish to create a new user record. Instantaneously the user account can be created in Active Directory. There is no longer a need to schedule processes (*limitations will be defined later).</p>
<p>
UMRA gives several methods to create and modify information in AD. UMRA Forms is similar to a website that someone can use, but it&rsquo;s run in a standalone application provided by <a href="http://www.tools4ever.com/">Tools4Ever</a>. Automation is a module provided by UMRA to schedule projects for execution at certain times. This is useful for projects that use a lot of network bandwidth (like moving student accounts and copying folders en masse). Automation also provides a COM object to use with other programs, like the web or SQL triggers.</p>
<p>
With the automation module, in association with the forms module for UMRA, SQL can utilize the same projects that a website, automation or form project might use and can maintain code reusability.</p>

<h1>Limitations</h1>

<p>OLE automation allows SQL administrators with sysadmin privileges to perform tasks on the operating system environment. This is a known limitation with SQL Server, but if you trust your administrators then there is nothing to be concerned about, since it only gives them access to programs already on the SQL server</p>
<p>
When executing a UMRA project through Triggers, execution time must be taken into consideration. For tasks that take a long time, like copying folders, it would be more appropriate to store the task in a database table for later processing. The scripts can be modified to perform the faster tasks during the Trigger execution and insert the long tasks into a process queue (in SQL). This is also better for network traffic since you would want these tasks happening in the off-peak hours.</p>
<p>
<h1>SQL Server Configuration</h1>
<p>
* All tasks should be performed on the SQL server or by using the SQL management tools pointed to the SQL server.</p>
<ol>
    <li>Open the <b>SQL Server Surface Area Configuration tool</b></li>
    <li>If a Program Compatibility Assistant window pops up press &ldquo;Run program&rdquo;</li>
    <li>Click the Surface Area Configuration for Features link</li>
    <li>Expand the SQL server -&gt; Database Engine and enable OLE Automation by checking the appropriate checkbox. Close the SAC tool when complete.</li>
</ol>

<img height="358" width="477" border="0" alt="SQL Surface Area Configuration for UMRA COM Automation" src="/wp-content/uploads/2012/02/SACCOMConfiguration.png" /><br />

<h1>SQL Database Configuration</h1>
<ol>
    <li>Within SQL Management Studio, expand the desired database and table.</li>
</ol>
<img border="0" alt="Table selection for creating a trigger in SQL Management Studio" src="/wp-content/uploads/2012/02/SMSTriggerTableSelection.png" />
<ol start="2">
    <li>Right-click on the Triggers folder and select &ldquo;New Trigger&rdquo;</li>
    <li>Modify the CREATE TRIGGER statement to match your desired results, and then execute the blank statement. An example of the modified CREATE statement is below.</li>
</ol>
<pre class="prettyprint">
CREATE TRIGGER TriggerCreate
ON TriggerDemo.dbo.Users
AFTER INSERT</pre>

<h1>Trigger Modification for UMRA integration</h1>
<ol>
    <li>Expand or refresh the Triggers folder in SQL Management Studio to view the new trigger. Double-click on it to modify the code.</li>
    <li>Paste in the supplied code and modify the supplied code in the appropriate areas and execute the ALTER TRIGGER statement:</li>
</ol>
<pre class="prettyprint">
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go

-- =============================================
-- Author:		Allan Bogh
-- Create date: 1/20/2009
-- Description:	UMRA Create User Trigger
-- =============================================
ALTER TRIGGER [TriggerCreate] 
	ON  [TriggerDemo].[dbo].[Users] 
	AFTER INSERT
AS 

-- required variable declarations --
DECLARE @UMRAScript varchar(64)
DECLARE @UMRAServer varchar(64)
DECLARE @UMRAPort int

-- EDIT THESE VALUES AS NECESSARY --
SET @UMRAScript = ''TriggerDemo''
SET @UMRAServer = ''YourServerName''
SET @UMRAPort = 56814

-- EDIT THESE VARIABLE NAMES TO MATCH THE SQL COLUMN INFORMATION --
-- WHICH WILL BE PASSED TO UMRA --
DECLARE @FirstName varchar(50)
DECLARE @LastName varchar(50)
DECLARE @Department varchar(50)
DECLARE @EmployeeId int

-- EDIT THESE VARIABLES AND SELECT STATEMENTS TO MATCH --
-- THE INFORMATION COMING FROM THE DATABASE --
SET @FirstName = (SELECT FirstName FROM inserted)
SET @LastName = (SELECT LastName FROM inserted)
SET @Department = (SELECT Department FROM inserted)
SET @EmployeeId = (SELECT ID FROM inserted)

-- DO NOT EDIT THE FOLLOWING VARIABLES
DECLARE @object int
DECLARE @umra int
DECLARE @ErrorSource varchar(255)
DECLARE @ErrorDesc varchar(255)

EXEC @umra = sp_OACreate ''UmraCom.UMRA'',@object OUT

IF @umra = 0
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;
	------------ DO NOT EDIT BELOW ---------------
	----------------------------------------------
	EXEC @umra = sp_OAMethod @object,''Connect'',NULL,@UMRAServer,@UMRAPort
	IF @umra &lt;&gt; 0 
	BEGIN
		EXEC sp_OAGetErrorInfo @object,@ErrorSource OUT, @ErrorDesc OUT
		SELECT ''Error occurred calling object: ''+ @ErrorSource + '' '' + @ErrorDesc
	END
	------------ DO NOT EDIT ABOVE ---------------
	----------------------------------------------
	ELSE
	BEGIN 
		--EDIT the UMRA variable names and associated SQL variables here------------
		----------------------------------------------------------------------------
		EXEC @umra = sp_OAMethod &nbsp; @object,''SetVariableText'',NULL,''%FirstName%'',@FirstName
		EXEC @umra = sp_OAMethod &nbsp; @object,''SetVariableText'',NULL,''%LastName%'',@LastName
		EXEC @umra = sp_OAMethod &nbsp; @object,''SetVariableText'',NULL,''%Department%'',@Department
		EXEC @umra = sp_OAMethod &nbsp; @object,''SetVariableText'',NULL,''%EmployeeId%'',@EmployeeId
		--EDIT ABOVE----------------------------------------------------------------
		----------------------------------------------------------------------------

		-------------DO NOT EDIT BELOW -------------
		--------------------------------------------
		IF @umra &lt;&gt; 0 
		BEGIN
			EXEC sp_OAGetErrorInfo @object,@ErrorSource OUT, @ErrorDesc OUT
			SELECT ''Error occurred calling object: ''+ @ErrorSource + '' '' + @ErrorDesc
		END
		ELSE
		BEGIN
			-- Executes the specified UMRA script
			EXEC @umra = sp_OAMethod @object,''ExecuteProjectScript'',NULL,@UMRAScript
			IF @umra &lt;&gt; 0 
			BEGIN
				EXEC sp_OAGetErrorInfo @object,@ErrorSource OUT, @ErrorDesc OUT
				SELECT ''Error occurred calling object: ''+ @ErrorSource + '' '' + @ErrorDesc
			END
		ELSE EXEC sp_OADestroy @object
		END
	END

END

SET NOCOUNT OFF;</pre>
<ol start="3">
    <li>Test trigger by inserting, updating, or deleting rows from the database depending on which triggers you have in place. Check the UMRA logs or AD for project execution.</li>
</ol>
<h1>Testing the Trigger</h1>
<ol>
    <li>Create a project in UMRA called &ldquo;TriggerTest&rdquo; (or whatever name you might have changed it to). An example project is illustrated below (very simple):</li>
</ol>
<img border="0" alt="UMRA project executed by a SQL Trigger" src="/wp-content/uploads/2012/02/UMRATriggerDemo.png" /><br />
<ol start="2">
    <li>Set the security for the project by Right-clicking the script and selecting Project Script Properties, then Security (include your account or group, and the SQL server account).</li>
    <li>Open SQL Management Studio or the application which submits new records to your table.</li>
    <li>Insert a new record into the table if you created an INSERT trigger, update a record if you created an UPDATE trigger, or delete a record if you created a DELETE trigger.</li>
    <li>Open the UMRA logs under <em>C:\\Program Files\\UmraService\\Log\\</em> (the newest modified file is the appropriate log file). Scroll to the bottom of the file and view the messages.</li>
</ol>
<h1>Extended method</h1>

<p>The example provided includes all of the programming for UMRA execution inside the Trigger. This was done for ease of understanding. The UMRA code can also be placed in a Stored Procedure, which is executed by the Trigger. This would allow for more code in the Trigger to determine what information has changed and needs to be sent to UMRA.</p>
]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/02/09/integrating-real-time-sql-triggers-to-manage-active-directory-accounts-with-umra-2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Where did CSS and JavaScript go wrong?</title>
		<link>http://opencodeproject.com/2012/02/03/where-did-css-and-javascript-go-wrong/</link>
		<comments>http://opencodeproject.com/2012/02/03/where-did-css-and-javascript-go-wrong/#comments</comments>
		<pubDate>Fri, 03 Feb 2012 20:24:11 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[CSS]]></category>
		<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=113</guid>
		<description><![CDATA[Ahhhhhhhh!!! That&#8217;s me pulling out my hair every time I start working with divs, CSS, and the DOM&#8217;s innerHTML property using JavaScript. I can never figure out if I&#8217;m doing something wrong or if someone somewhere made a huge mistake &#8230; <a href="http://opencodeproject.com/2012/02/03/where-did-css-and-javascript-go-wrong/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Ahhhhhhhh!!! That&#8217;s me pulling out my hair every time I start working with divs, CSS, and the DOM&#8217;s innerHTML property using JavaScript. I can never figure out if I&#8217;m doing something wrong or if someone somewhere made a huge mistake when they made innerHTML.<br /> <span id="more-113"></span> <br /> Here&#8217;s the situation: I&#8217;ve made a website (you&#8217;re looking at it &#8211; <a href="http://www.opencodeproject.com">opencodeproject.com</a>) and I want to make a JSON-enabled search system which replaces the text in the main DIV with the results from the search. Seems like simply sticking the new text where the old stuff was would work, but here comes the catch. The website that I&#8217;ve developed has 3 columns &#8211; among the hardest to get right with CSS and the start of endless debates as to why tables are better than CSS in this situation &#8211; but the center column is dynamic so all columns need to be as big as the biggest column in order to look like a true 3 column layout. I&#8217;ve used some JavaScript which tests each column&#8217;s size given the data it contains and resizes the other columns to match the size. It&#8217;s very nice when it works.</p>
<h1>Example:</h1>
<p>Column A is 250px tall, Column B is only 100px, Column C is 400px. All columns must be the same size (so that background colors match). I test A with B and C and change A to be 400px. I do the same for columns B and C, changing when necessary.</p>
<h1>What&#8217;s the problem?</h1>
<p>I&#8217;ve found (with FireFox at least) that innerHTML doesn&#8217;t always update all aspects of the DOM when it gets written to. This tends to be found if you write a layer with an ID to the innerHTML property and try to reference that layer with a JavaScript function previously written on the page. The JavaScript function will complain that the object couldn&#8217;t be found. You know it&#8217;s there because you wrote it there, you can see it, but why doesn&#8217;t the DOM know about it?!?<br /> <br /> In theory at least, the DOM tree should be rebuilt from the parent div (which contains the innerHTML property) to the next sibling element in the tree. This apparently doesn&#8217;t happen in Firefox. Essentially what you&#8217;re writing to innerHTML is dumb text that gets rendered as HTML on the webpage. It&#8217;s useless if you want to do anything with it &#8211; modify it &#8211; make it dynamic.</p>
<h1>How do you fix this?</h1>
<p>There&#8217;s only one way in FireFox to fix this innerHTML bug (is it a bug really?) and that&#8217;s to write directly to the DOM. This takes a lot of patience and understanding of concepts that need not be understood. It also takes a lot of JavaScript and is generally slow to run. Anyway, why should the web developer have to worry about how the DOM works when the DOM methods work so much better. Heck, the browser builds the page when it loads, why can&#8217;t it just rebuild part of the page when innerHTML is rewritten?</p>
<h1>Can I see an example?</h1>
<p>I have some lovely pictures of my test environment for your benefit. At the end I will describe my final fix (if I have one).<br /> 
<br /> 
<a href="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox.png"><img src="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox.png" alt="Correctly displayed webpage." width="400" height="204" border="0" /></a><br /> 

Correctly displayed webpage. Notice the 3 column layout &#8211; Column A is modified, Column B is modified, Column C defines the height of A and B.<br /> <br /> 

<a href="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox-1.png"><img src="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox-1.png" alt="Incorrectly displayed webpage." width="400" height="214" border="0" /></a><br /> 

Incorrectly displayed webpage. Notice the content in the center column (B) is hovering over the page. The JavaScript function for modifying the column sizes had ran, but it could not determine any change in the center div&#8217;s height due to the DOM properties not being updated by FireFox.<br /> <br /> 

<a href="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox-4.png"><img src="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox-4.png" alt="Outline of correctly displayed webpage." width="400" height="209" border="0" /></a><br /> 

Outline of the layers containing the correctly displayed elements. Notice the center column is a large div containing a smaller dynamic div which has some padding and contains the articles.<br /> <br /> 

<a href="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox-5.png"><img src="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox-5.png" alt="Outline of the incorrectly displayed webpage" width="400" height="211" border="0" /></a><br /> 

Outline of the incorrectly displayed webpage. Notice that the padded div is out of the boundary of the parent div, hovering over anything in sight. Like a belly hanging over some pants. The main div should have dynamically resized.<br /> <br /> 

<a href="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox-2.png"><img src="/wp-content/uploads/2012/02/Screenshot-The-Open-Code-Project-Mozilla-Firefox-2.png" alt="" width="400" height="252" border="0" /></a><br /> 

This is what it should look like when there&#8217;s a bunch of search results.</p>

<h1>Update</h1>
<p>OK, so I&#8217;ve just discovered the holy grail secret that the Internet has been hiding. How do you dynamically resize a div if the child information has been rewritten? Note: this only works if the child element is a container div of more items.</p>
<pre class="prettyprint">function allocateSidebarHeight() {
                //gets the elements to modify
                var contentDom = document.getElementById("main");
                var sidebarDom = document.getElementById("subnav");
                var rightSidebarDom = document.getElementById("main_right");

                //tests the height of the main div 
                //(the main div controls the background heights of the other 2)
                if((contentDom.offsetHeight) &lt; sidebarDom.offsetHeight) {
                    contentDom.style.height = sidebarDom.offsetHeight+"px"; 
                }
                if((contentDom.offsetHeight) &lt; rightSidebarDom.offsetHeight){

                    contentDom.style.height = (rightSidebarDom.offsetHeight)+"px";
                }
                //this is the secret. 
                //contentDom.childNodes[0] is blank text
                //contentDom.childNodes[1] is the padded div
                //contentDom.childNodes[2] is blank text
                //the last &amp;&amp; is to make sure the height has previously been set from the page load
                if(contentDom.childNodes[1] &amp;&amp; 
                      contentDom.childNodes[1].offsetHeight &gt; contentDom.style.height.replace('px','') &amp;&amp; 
                      contentDom.style.height != ''){
                    contentDom.style.height = contentDom.childNodes[1].offsetHeight+"px";
                }
            }</pre>
<p>A thought occurred to me after thinking about why it&#8217;s possible to reference the offsetHeight of the child element when I claimed that the DOM doesn&#8217;t get updated. This seems to make my point invalid, but it does allow me to test a theory. If I were to remove the parent div from the DOM, add the child element using the innerHTML property, then rewrite the parent div back into the correct location of the DOM , would that allow the parent element&#8217;s offsetHeight to be updated? Do we have to strip the parent from the known tree before adding children to it? I will update on this when I have an answer.</p>
]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/02/03/where-did-css-and-javascript-go-wrong/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Converting PDFs to PNGs</title>
		<link>http://opencodeproject.com/2012/02/03/converting-pdfs-to-pngs/</link>
		<comments>http://opencodeproject.com/2012/02/03/converting-pdfs-to-pngs/#comments</comments>
		<pubDate>Fri, 03 Feb 2012 18:03:35 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[Open Source]]></category>
		<category><![CDATA[CutePDF]]></category>
		<category><![CDATA[Ghostscript]]></category>
		<category><![CDATA[GIMP]]></category>
		<category><![CDATA[PDF to PNG]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=109</guid>
		<description><![CDATA[One of the problems with basic Windows PDF document viewers is you can&#8217;t save as another file format. This is most likely for copyright reasons, but what if you need to copy the contents of a PDF image document to &#8230; <a href="http://opencodeproject.com/2012/02/03/converting-pdfs-to-pngs/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>One of the problems with basic Windows PDF document viewers is you can&#8217;t save as another file format. This is most likely for copyright reasons, but what if you need to copy the contents of a PDF image document to a Word document?<br />
<span id="more-109"></span>
<br />
I bring this up because I came across a scenario where I needed to copy a PDF to Word for a client (additionally, this could work for <a href="http://usa.autodesk.com/adsk/servlet/index?siteID=123112&amp;id=2704278">AutoCAD</a>&nbsp;designs). The PDF contained several cross functional flow charts with no other information. Visio contained a print to PDF option in the print menu because I installed a free PDF printer called <a href="http://www.cutepdf.com/Products/CutePDF/writer.asp">CutePDF</a>. Once the PDF was created I could include it in an email attachment, but I also wanted to include the reduced-size images in the project documentation. <br />
<br />
I decided to use PNG because it would offer the best compression and sharpness in Word, but there was no way to easy save the PDF as a PNG. I did some searching and found plenty of programs that I could buy for around <a href="http://www.brothersoft.com/pdf-to-png-72357.html">$59</a>, but I knew PDFs had been out for some time and the open source community must have something up their collective sleeves. I found a forum post on the topic which mentioned that GIMP could read PDF files. This was the golden ticket.<br />
<br />
I downloaded GIMP for Windows, which I&#8217;m now pretty used to using GIMP in Ubuntu, and installed it. Because I had previously installed CutePDF I didn&#8217;t need any other open source PDF software, but you may need <a href="http://www.cutepdf.com/download/converter.exe">the free GPL Ghostscript</a>. Once installed I opened the PDF one page at a time (it was only 4 pages long) and saved each page as a PNG. I dragged the PNG files to my Word document and resized as necessary.<br />
<br />
This goes to show that open source solutions are as strong as proprietary paid software, could get more downloads because it&#8217;s free (webpage advertising can make money), and can often be easier to use than proprietary software. Not that saving each page individually was easier than a program that can do a batch of pages, but I could very well develop a script to perform the batch job against the GIMP.</p>]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/02/03/converting-pdfs-to-pngs/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Fixing Home Directories with Powershell</title>
		<link>http://opencodeproject.com/2012/01/31/fixing-home-directories-with-powershell/</link>
		<comments>http://opencodeproject.com/2012/01/31/fixing-home-directories-with-powershell/#comments</comments>
		<pubDate>Tue, 31 Jan 2012 17:12:45 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[Active Directory]]></category>
		<category><![CDATA[Powershell]]></category>
		<category><![CDATA[ACL]]></category>
		<category><![CDATA[UMRA]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=91</guid>
		<description><![CDATA[I had a customer that reported that some of his student&#8217;s home folders were missing and ending up on someone else&#8217;s account. This is a very bad thing when it comes to large school districts, as you could imagine. I &#8230; <a href="http://opencodeproject.com/2012/01/31/fixing-home-directories-with-powershell/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[I had a customer that reported that some of his student&#8217;s home folders were missing and ending up on someone else&#8217;s account. This is a very bad thing when it comes to large school districts, as you could imagine. I identified the problem immediately and fixed it but that left us with a number of home directories that were already copied to new locations. I had to find the original owners of these directories and produce a list of the owner and the new directory name. In the process I also produced a list of the students where they had a home directory attribute set but the home directory was missing.<br />
<span id="more-91"></span><br />
My first inclination was to use VBScript since I&#8217;ve created a script to list file security before. This turned out to be troublesome since VBScript requires some special functions that are becoming harder to find for listing the ACL on remote folders and files. I found the code but VBScript refused to run it properly, which  was obviously because I needed something a little different. If I had all the time in the world then I could have tinkered with it more, but I was under the gun.<br />
<br />
I decided to try Powershell since I knew that it could get ACLs, and since it worked on remote file systems natively.<br />
<br />
The file below takes a folder or filename and a username as parameters. It then gets the ACL from the folder and formats it into a useful list. The resulting table is a Powershell (C#) object which is output to the screen in a text form, but it&#8217;s not available to the code as a string object. Instead we have to convert it to a string. <br /><br />

<pre>
Path   : Microsoft.PowerShell.Core\FileSystem::\\server\UserFiles\School\GradeN\12345
Owner  : DOMAIN\Domain Admins
Group  : DOMAIN\Domain Users
Access : DOMAIN\Domain Users Allow  ReadAndExecute, Synchronize
         DOMAIN\98765 Allow  FullControl
         Everyone Allow  ReadAndExecute, Synchronize
         NT AUTHORITY\SYSTEM Allow  FullControl
         DOMAIN\Administrator Allow  FullControl
         BUILTIN\Administrators Allow  FullControl
Audit  :
Sddl   : (bunch of GUIDs and Hex here)
</pre>

Once it&#8217;s a string we find the part that says &#8220;Access&#8221; because this is the ACL of the folder [1]. Since there are other sections beyond the Access section, we have to find the position of the &#8220;Audit&#8221; section and remove everything from that point on from the resulting string [2].<br />
<br />
<strong>1</strong>
<pre>
Access : DOMAIN\Domain Users Allow  ReadAndExecute, Synchronize
         DOMAIN\98765 Allow  FullControl
         Everyone Allow  ReadAndExecute, Synchronize
         NT AUTHORITY\SYSTEM Allow  FullControl
         DOMAIN\Administrator Allow  FullControl
         BUILTIN\Administrators Allow  FullControl
Audit  :
Sddl   : (bunch of GUIDs and Hex here)
</pre>

<strong>2</strong>
<pre>
Access : DOMAIN\Domain Users Allow  ReadAndExecute, Synchronize
         DOMAIN\98765 Allow  FullControl
         Everyone Allow  ReadAndExecute, Synchronize
         NT AUTHORITY\SYSTEM Allow  FullControl
         DOMAIN\Administrator Allow  FullControl
         BUILTIN\Administrators Allow  FullControl
</pre>

We then replace the unnecessary bits (&#8220;Access : &#8221; and &#8220;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8221;). This could be done differently, maybe using trim, but it was a quick hack.<br /><br />

<pre>
DOMAIN\Domain Users Allow  ReadAndExecute, Synchronize
DOMAIN\98765 Allow  FullControl
Everyone Allow  ReadAndExecute, Synchronize
NT AUTHORITY\SYSTEM Allow  FullControl
DOMAIN\Administrator Allow  FullControl
BUILTIN\Administrators Allow  FullControl
</pre>

Now that we have a nice list we have to split that into a usable array so we can check each line&#8217;s username. We split on the &#8220;`n&#8221; character, which is the newline in Powershell. <br /><br />

<h1>The Loop</h1>

We were looking for any domain accounts, so anything that didn&#8217;t start with the domain name was skipped. Because This customer&#8217;s student usernames were the same as their student ID, which were integer numbers, if the username wasn&#8217;t an integer then we didn&#8217;t care about it.<br />
<br />
If the username on the folder is different than the username passed into the script then we know that a different user has access to the student&#8217;s home folder. We output this to the console.<br />
<br />

<strong>FindIncorrectSecurity.ps1</strong>
<pre class="prettyprint">
#two parameters are provided on this script:
# -file and -user
param($file,$user)

#Get the ACL for the file
$output = get-acl "$file" | format-list

#Convert the output to a string
$output = Out-String -InputObject $output

#Format the string. Get the string from "Access" to the end
$output = $output.SubString($output.IndexOf("Access"))

#Format the string. Do not get anything past "Audit"
$output = $output.SubString(0,$output.IndexOf("Audit"))

#Cleanup the resulting string
$output = $output.Trim().Replace("Access : ","").Replace("         ","")

#Split the ACL into an array by the newline characters
$outputArr = $output.Split("`n")

#Loop through each line
foreach($out in $outputArr){
    #Only process things starting with "DOMAIN"
    if($out.StartsWith("DOMAIN")){
        #Format the string. Only get the DOMAIN\Username part of the string
        $out = $out.SubString(0,$out.IndexOf(" ")).Replace("DOMAIN\","")

        #Only process usernames that are integers
        if($out -match "^\d+$"){
            #Check if username is the same as the -user parameter
            if($out -match $user){ 
                #commented out, do nothing
		#$out+": "+$file 
            }else{
                #output username and folder
                $out+": "+$file
            }
        }
    }
}
</pre>

To run the script above, we get the list of students in the domain, then for each student we get the homeDirectory attribute from Active Directory. This attribute is stored as &#8220;%HomeDirectory%&#8221;. The &#8220;%StudentNumber%&#8221; variable is the same as the student&#8217;s username in this case. <br />
<br />
Within a program called UMRA, we&#8217;re able to check if the home directory exists. You can do the same in any language. For students that have a homeDirectory attribute filled in but no home directory on the file system, we log them to our output text file with the username and the home directory attribute value.<br />
<br />
Any student with a home directory attribute filled in and a folder that exists we run through our Powershell script and get the Powershell output to log to our text file. <br />
<br />
We will use the command below:<br />

<pre>powershell "C:\Users\umraconsultant\Desktop\filesecurity.ps1" -file "%HomeDirectory%" -user  "%StudentNumber%"</pre>

The resulting text file will look something like this:<br />

<pre>
""\\server\UserFiles\School\GradeN\98765" does not exist."
"98765: \\server\UserFiles\School\GradeN\12345"
</pre>

The first line shows that a user with the username 98765 is missing a home directory. This can be for any reason but we&#8217;ll assume that a script copied it to someone else and deleted the original without cleaning up the owner&#8217;s homeDirectory attribute.<br />
<br />
The second line shows a home directory with an incorrect user on it. It shows the username is 98765 and the folder is &#8220;\12345&#8243;. This tells us that the user 98765 owned the folder before it was moved to the &#8220;\12345&#8243; location. <br />
<br />
We can then use the list to manually clean up the missing and moved folders.]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/01/31/fixing-home-directories-with-powershell/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Uploading Logitech Alert Security Video to Dropbox using PHP</title>
		<link>http://opencodeproject.com/2012/01/30/uploading-logitech-alert-security-video-to-dropbox-using-php/</link>
		<comments>http://opencodeproject.com/2012/01/30/uploading-logitech-alert-security-video-to-dropbox-using-php/#comments</comments>
		<pubDate>Mon, 30 Jan 2012 08:14:55 +0000</pubDate>
		<dc:creator>Allan Bogh</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Alert]]></category>
		<category><![CDATA[Dropbox]]></category>
		<category><![CDATA[Logitech]]></category>

		<guid isPermaLink="false">http://opencodeproject.com/?p=74</guid>
		<description><![CDATA[Some time ago my house was broken into. My wife had left for a quick walk at a nearby park with our dog and within 30 minutes, at 10:30 in the morning, some people broke through our side-entry garage door &#8230; <a href="http://opencodeproject.com/2012/01/30/uploading-logitech-alert-security-video-to-dropbox-using-php/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[Some time ago my house was broken into. My wife had left for a quick walk at a nearby park with our dog and within 30 minutes, at 10:30 in the morning, some people broke through our side-entry garage door and stole anything electronic that they could carry, as well as loaded weapons in a small safe. When my wife came home they escaped out the garage door again, leaving the house a mess. After this incident we decided to get some security cameras.<br />
<span id="more-74"></span>
<br />
We chose the Logitech Alert system because they were semi-wireless and had a handy Android and website internet utility provided by Logitech. The system includes a master unit which connects to a network switch and each camera includes a wireless powerline connection (plugs into wall outlet, data is sent wirelessly) with a cable to the camera. The cameras can be loaded with a micro SD card in case the cables are broken and the internet is knocked out. The system can be configured to send a picture to your email address, however these pictures tend to be a bit blurry so Logitech also provides short video feeds.<br />
<br />
The problem is Logitech didn&#8217;t originally include a way to store these video feeds offsite. If your computer was stolen then your videos were gone forever, and all images of the thieves. Now Logitech has updated their software to include a Dropbox feature, however it comes at a price. Here&#8217;s a simple solution that does not come at a price and only requires you to run a scheduled task or cron job every 2 minutes.<br />
<br />
<h1>Setup</h1>

Configure Logitech Alert to store the videos in a folder. In Windows this is as easy as choosing a folder on a rather large drive (I&#8217;ve devoted 300GB to video storage). In Linux you will need to run a Windows VM, Wine won&#8217;t work for this. Within the Windows VM you can map a Linux shared folder to the Z: drive, for example. Each video file is about 5MB for 30 seconds. 300GB is about 21 days worth of video.<br />
<br />
I&#8217;ve chosen a folder in my Linux machine called &#8220;/home/myuser/Videos/Security Cameras/Logitech Alert Recordings&#8221;. In the Windows VM it&#8217;s called &#8220;Z:\Logitech Alert Recordings&#8221;.<br />
<br />
Install PHP (<a href="http://windows.php.net/download/">Windows</a>) on your computer. For Linux you will have to install php-cli (for Debian-based Linux: sudo apt-get install php5-cli).<br />
<br />
Setup <a href="https://www.dropbox.com/">Dropbox</a> and point it to a &#8220;Dropbox&#8221; folder. Create a &#8220;Security Cameras&#8221; folder beneath this folder.<br />
<br />
<h1>The Script</h1>

<strong>Note</strong>: scroll past the script to see how to configure the scheduling options.<br />
<br />
This PHP script pushes the latest 2GB (about) worth of video to Dropbox&#8217;s free account. Dropbox allows up to 2GB in its free accounts, I&#8217;ve chosen to fill about 1875MB of the 2000MB. This is 375  of the latest files. The script will first do a cleanup operation on the old files before uploading the new ones.<br />
<br />
There are 3 variables which control the entire file (Windows will use different folder paths):<br />
<br />
<pre class="prettyprint">
$sourceFolder = "/home/myuser/Videos/Security Cameras/Logitech Alert Recordings"; //the videos folder
$dropboxFolder = "/home/myuser/Dropbox/Security Cameras"; //omit the trailing slashes
$numberOfFiles = 375; //2000MB / 5MB = 400 files. Subtract 25 files to maintain adequate folder space.
// = 3.125 hours of video
</pre>
Once these variables are configured the script should work. I recommend placing this script in your &#8220;/home/myuser/Videos/Security Cameras&#8221; folder. I&#8217;ve named mine &#8220;sync.php&#8221;.
<br /><br />
<strong>sync.php</strong>:<br />
<pre class="prettyprint">
&lt;?php
$sourceFolder = "/home/myuser/Videos/Security Cameras/Logitech Alert Recordings";
$dropboxFolder = "/home/myuser/Dropbox/Security Cameras"; //omit the trailing slashes
$numberOfFiles = 375; //2000MB / 5MB = 400 files. Subtract 25 files to maintain adequate folder space.
// = 3.125 hours

//finds all mp4s in the recording directory for all cameras.
//the result is a list of files where each line reads:
//	/dir/filename.mp4	2011-03-27+05:25:45.1833860010 
//(that's a tab between the filename and date)
$filearray = scan_directory_recursively($sourceFolder,'mp4');

//sort that shit.
$dates = array();
foreach($filearray as $key=>$file){
	$dates[$key] = $filearray[$key]['created']; //position 5 (path, name, extension, size, created)
}
array_multisort($dates,SORT_DESC,$filearray); //sort it
$filearray = array_splice($filearray,0,$numberOfFiles); //remove excess files

//loop through files to copy
$filesToCopy = array();
$fileNameArray = array();
foreach($filearray as $file){
	//split the /folder/file path to extract the filename
	$fileArr = explode("/",$file['path']);
	$filename = $fileArr[count($fileArr)-1];
	$newfilename = date('Y-m-d\TH:i:s',$file['created']).' - '.$filename;
	//an array for the new filename
	$fileNameArray[] = $newfilename;
	//an array for the full path
	$filesToCopy[] = $file['path'];
}

$dropFiles = scan_directory_recursively($dropboxFolder);
$dropFileNames = array();
foreach($dropFiles as $file){
	$dropFileNames[] = $file['name'];
}

//the difference of the file lists should be all files that must be deleted from the drop directory
$diff = array_diff($dropFileNames,$fileNameArray);

//remove each file that doesn't belong in the drop directory.
foreach($diff as $file){
	echo "Deleting {$file}...\n";
	unlink("{$dropboxFolder}/{$file}");
}

if(count($diff) > 0){
	echo "\n";
}

//scan the dir again!
$dropFiles = scan_directory_recursively($dropboxFolder);
$dropFileNames = array();
foreach($dropFiles as $file){
	$dropFileNames[] = $file['name'];
}
//the difference of the file lists should be all files that must be deleted from the drop directory
$diff = array_diff($fileNameArray,$dropFileNames);

foreach($diff as $key=>$file){
	echo "Copying {$file}...\n";
	copy($filesToCopy[$key],$dropboxFolder.'/'.$file);
}




function scan_directory_recursively($directory, $filter=FALSE)
{
	
	$directory_tree = array();

	// if the path has a slash at the end we remove it here
	if(substr($directory,-1) == '/')
	{
		$directory = substr($directory,0,-1);
	}

	// if the path is not valid or is not a directory ...
	if(!file_exists($directory) || !is_dir($directory))
	{
		// ... we return false and exit the function
		return FALSE;

	// ... else if the path is readable
	}elseif(is_readable($directory))
	{
		// we open the directory
		$directory_list = opendir($directory);

		// and scan through the items inside
		while (FALSE !== ($file = readdir($directory_list)))
		{
			// if the filepointer is not the current directory
			// or the parent directory
			if($file != '.' &#038;&#038; $file != '..')
			{
				// we build the new path to scan
				$path = $directory.'/'.$file;

				// if the path is readable
				if(is_readable($path))
				{
					// we split the new path by directories
					$subdirectories = explode('/',$path);

					// if the new path is a directory
					if(is_dir($path))
					{
						// add the directory details to the file list
						$directory_tree = array_merge((array)$directory_tree,scan_directory_recursively($path, $filter));

					// if the new path is a file
					}elseif(is_file($path))
					{
						// get the file extension by taking everything after the last dot
						$extension = end(explode('.',end($subdirectories)));

						// if there is no filter set or the filter is set and matches
						if($filter === FALSE || $filter == $extension)
						{
							// add the file details to the file list
							$directory_tree[] = array(
								'path'      => $path,
								'name'      => end($subdirectories),
								'extension' => $extension,
								'size'      => filesize($path),
								'kind'      => 'file',
								'created'   => filemtime($path));
						}
					}
				}
			}
		}
		// close the directory
		closedir($directory_list); 

		// return file list
		return $directory_tree;

	// if the path is not readable ...
	}else{
		// ... we return false
		return FALSE;	
	}
}

?&gt;
</pre>
<h1>Scheduling</h1>

<strong>Linux</strong><br />
Edit your crontab by typing in the Terminal &#8220;crontab -e&#8221;<br />
<br />
Paste the following code with your modifications:<br />
<br />
<pre>*/2 * * * * php "/home/myuser/Videos/Security Cameras/sync.php" # JOB_ID_4</pre>
<strong>Windows</strong><br />
Navigate to Start->Programs->Accessories->System Tools and choose Task Scheduler (Vista)<br />
<br />
You will have to create a task to run &#8220;php [Drive]:\Folder\sync.php&#8221; every hour, then go back to edit the advanced options using <a href="http://support.microsoft.com/kb/226795">these steps provided by Microsoft</a>.]]></content:encoded>
			<wfw:commentRss>http://opencodeproject.com/2012/01/30/uploading-logitech-alert-security-video-to-dropbox-using-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

