Saturday, May 08, 2010

Adapting to the ever changing drive letters...

...by using unique volume labels.

Okay, this isn't a complete thought yet, just some ideas I've mulled over and found a solution for for myself, but feel free to adapt and play with it. The reason I came up with this is because I picked up a new toy, a pogoplug (v2) and it provides me with network attached storage of multiple USB Drives (External Hard Disks or USB Keys). That's great, and it also provides for the automated backup of files/folders on my PC to the device, so why would I want to control it any further?

Because I can. The drive letters can change from time to time, depending on the timing of this or that being able to adapt to the changing drive letter seemed like a good idea. So, I fired up Notepad++ and wrote myself a VBScript tool that can do a bunch of things, but in this instance it can locate the drive letter for the volume name and I can parse that using CScript (command-line VBScript, vs. WScript, which would run graphical). Now I first ran cscript //H:CScript to ensure that it runs like a "DOS App" but you could easily change the batch file portion of this to simply run this in cscript. The follwing is the VBScript:

Option Explicit
' Find My Drive (ListDrives.vbs)

DIM strVolume, strFound, objDictionary, objWMIService, colDisks, objDisk, strComputer
DIM flagHideList, strDrive, wshShell, wshSystemEnv, wshCurrentEnv, strSetVar
DIM i

flagHideList=false
strFound=""
strVolume=""
strComputer = "."

Set wshShell = CreateObject( "WScript.Shell" )
Set wshCurrentEnv = wshShell.Environment( "VOLATILE" ) ' PROCESS, SYSTEM, USER and VOLATILE
Set objDictionary = CreateObject("Scripting.Dictionary")
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

If Wscript.Arguments.Count = 0 Then
    ' Do Proceed as Normal
Else
    For i = 0 to Wscript.Arguments.Count - 1
  If lcase(Wscript.Arguments(i)) = "/volume" Then
            if i+1 <= Wscript.Arguments.Count then
    strVolume=Wscript.Arguments(i+1)
   end if
  end if
  If lcase(Wscript.Arguments(i)) = "/setvar" Then
            if i+1 <= Wscript.Arguments.Count then
    strSetVar=Wscript.Arguments(i+1)
   end if
  end if
  If lcase(Wscript.Arguments(i)) = "/hidelist" Then
   flagHideList=true
  end if
 next
End If

Set colDisks = objWMIService.ExecQuery("Select * from Win32_LogicalDisk")

For Each objDisk in colDisks
    objDictionary.Add objDisk.DeviceID, objDisk.VolumeName
 if NOT flagHideList then WScript.Echo objDisk.DeviceID & " " & objDisk.VolumeName
Next

For i = 67 to 90
    strDrive = Chr(i) & ":"
    If objDictionary.Exists(strDrive) Then
  if strVolume=ObjDictionary.Item(strDrive) then strFound=Chr(i) & ":"
    End If
Next
if strFound<>"" then
 WScript.Echo strVolume & "=" & strFound
 if strSetVar<>"" then
  ' Display the current value
  WScript.Echo strSetVar & "=" & wshCurrentEnv( strSetVar )
  ' Set the environment variable
  wshCurrentEnv( strSetVar ) = strFound
  WScript.Echo strSetVar & "=" & wshCurrentEnv( strSetVar )
 end if
end if
WScript.Quit 0
...and yes, this could be enhanced and/or streamlined.

So. To find a Volume name "Tom's Drive" you would type:
ListDrives.vbs /volume "Tom's Drive"

Now that's cute, but useless beyond yourself... what you can do is automate it... throw this into a batch file:

FOR /F "usebackq tokens=1,2 skip=3 delims==" %%i IN (`ListDrives.vbs /volume "Tom's Drive" /hidelist`) DO SET DL=%%j 

or

FOR /F "usebackq tokens=1,2 skip=3 delims==" %%i IN (`cscript ListDrives.vbs /volume "Tom's Drive" /hidelist`) DO SET DL=%%j 

You can now use the environment variable %DL% as the drive letter. You can use this dealing with All sorts of USB Keys, or even a memory card. There are other tricks too, but this was just an idea.

Tuesday, April 06, 2010

Using T-SQL to Decode a Decimal (BIGINT) IP Address

A site I ran across stored an IP Address as a BIGINT decimal number, to reveal the more friendly IP Address use either of the following methods to extract the octets or the Friendly IP. These formulae will allow you to return the IP Address in a usable manner.

IPAddress IPOCTET1 IPOCTET2 IPOCTET3 IPOCTET4 IPAddressEX
2886730061 172 16 1 77 172.16.1.77
2886730059 172 16 1 75 172.16.1.75
2886730053 172 16 1 69 172.16.1.69
2886730046 172 16 1 62 172.16.1.62
2886730047 172 16 1 63 172.16.1.63
2886730303 172 16 2 63 172.16.2.63
,[IPAddress]
You can use:
, CAST( CAST([IPAddress]/POWER(256,3) AS BIGINT) AS VARCHAR(3) ) AS IPOCTET1 , CAST( CAST( CAST( [IPAddress]-CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) AS BIGINT)/POWER(256,2) AS BIGINT) AS VARCHAR(3)) AS IPOCTET2 , CAST( CAST( CAST([IPAddress] -CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) -CAST(CAST([IPAddress]-CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) AS BIGINT)/POWER(256,2) AS BIGINT)*POWER(256,2) AS INTEGER)/256 AS INTEGER) AS VARCHAR(3)) AS IPOCTET3 , CAST( [IPAddress]- (CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3)+ CAST(CAST([IPAddress]-CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) AS BIGINT)/POWER(256,2) AS BIGINT)*POWER(256,2)+ CAST( CAST( [IPAddress] -CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) -CAST(CAST([IPAddress]-CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) AS BIGINT)/POWER(256,2) AS BIGINT)*POWER(256,2) AS BIGINT)/256 AS BIGINT)*256) AS VARCHAR(3)) AS IPOCTET4
or use this to build a properly concatenated string.
, CAST( CAST([IPAddress]/POWER(256,3) AS BIGINT) AS VARCHAR(3) ) + '.' + CAST( CAST( CAST( [IPAddress]-CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) AS BIGINT)/POWER(256,2) AS BIGINT) AS VARCHAR(3)) + '.' + CAST( CAST( CAST([IPAddress] -CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) -CAST(CAST([IPAddress]-CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) AS BIGINT)/POWER(256,2) AS BIGINT)*POWER(256,2) AS INTEGER)/256 AS INTEGER) AS VARCHAR(3)) + '.' + CAST( [IPAddress]- (CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3)+ CAST(CAST([IPAddress]-CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) AS BIGINT)/POWER(256,2) AS BIGINT)*POWER(256,2)+ CAST( CAST( [IPAddress] -CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) -CAST(CAST([IPAddress]-CAST([IPAddress]/POWER(256,3) AS BIGINT)*POWER(256,3) AS BIGINT)/POWER(256,2) AS BIGINT)*POWER(256,2) AS BIGINT)/256 AS BIGINT)*256) AS VARCHAR(3)) AS IPAddressEX


...and Back

To find a printer, in this example by searching for one in SQL, that IP Address might seem fairly useless looking at the numbers involved. You could calculate it yourself by taking the Octets of the IP Address and using this formula:
16777216*Octet1 + 65536*Octet2 + 256*Octet3 + Octet4
...but we like automation.

DECLARE @IPAddr varchar(16); DECLARE @IPAddrAsDecimal BIGINT; DECLARE @IPAddrOctet1 BIGINT; DECLARE @IPAddrOctet2 BIGINT; DECLARE @IPAddrOctet3 BIGINT; DECLARE @IPAddrOctet4 BIGINT; DECLARE @intStartPos INT; DECLARE @intEndPoint INT SET @IPAddr='10.176.24.138'; --SET @IPAddr='172.16.2.66'; SELECT @IPAddrOctet1=CONVERT(BIGINT, LEFT(@IPAddr,CHARINDEX('.', @IPAddr)-1)); SELECT @intStartPos=(LEN(@IPAddrOctet1)+2); SELECT @intEndPoint=CHARINDEX('.', @IPAddr, @intStartPos); SELECT @IPAddrOctet2=CONVERT(BIGINT, SUBSTRING(@IPAddr,@intStartPos,@intEndPoint-@intStartPos)); SELECT @intStartPos=(LEN(@IPAddrOctet1)+LEN(@IPAddrOctet2)+3); SELECT @intEndPoint=CHARINDEX('.', @IPAddr, @intStartPos); SELECT @IPAddrOctet3=CONVERT(BIGINT, SUBSTRING(@IPAddr,@intStartPos,@intEndPoint-@intStartPos)); SELECT @intStartPos=(LEN(@IPAddrOctet1)+LEN(@IPAddrOctet2)+LEN(@IPAddrOctet3)+4); SELECT @intEndPoint=LEN(@IPAddr); SELECT @IPAddrOctet4=CONVERT(BIGINT, RIGHT(@IPAddr,@intEndPoint-@intStartPos+1)); SELECT @IPAddrOctet1 as Octet1, @IPAddrOctet2 as Octet2, @IPAddrOctet3 as Octet3, @IPAddrOctet4 as Octet4 SELECT @IPAddrAsDecimal=16777216*@IPAddrOctet1 + 65536*@IPAddrOctet2 + 256*@IPAddrOctet3 + @IPAddrOctet4; SELECT @IPAddrAsDecimal as IPAddressAsDecimal; --/* SELECT [IPAddress] ,[PrinterName] ,[PModelID] ,[SiteID] ,[MAC] ,[Location] ,[Approved] ,[Hits] ,[Agent] ,[TouchDate] FROM [oad].[dbo].[PrinterList] WHERE IPAddress = @IPAddrAsDecimal ; --*/

Sunday, August 16, 2009

Backing up 'My Pictures'

I use Picasa to manage my pictures, all of them. The problem I have and a preference is that while I keep some of them on my laptop, which is backed up weekly, I'd like the bulk of them archived to my external drives.

The following is a batch file (.CMD) that will archive my files from my laptop to my "T:" drive, then back up that collection, complete to my "G:" drive. It also tries to process the Picasa Album databases, but I'm not sure this is working ideally yet. When running this batch file ensure Picasa is not running, and relax.
@echo off
CLS
SET backupDest1=T:\Archive of My Pictures
SET backupDest2=G:\Backup of My Pictures
TITLE Backup Up Picasa Album Settings...
FOR /F "tokens=1,2,3,4,5 delims=/ " %%a in ('date /T') do SET backupDate=%%c%%b%%a
PUSHD "%USERPROFILE%\Local Settings\Application Data\Google\Picasa2Albums\"
IF NOT EXIST "%backupDest1%" MKDIR "%backupDest1%"
IF NOT EXIST "%backupDest1%\_AlbumInfo" MKDIR "%backupDest1%\_AlbumInfo"
IF NOT EXIST "%backupDest1%\_AlbumInfo\%backupDate%" MKDIR "%backupDest1%\_AlbumInfo\%backupDate%"
robocopy backup "%backupDest1%\_AlbumInfo\backup" *.* /XO /S /E /ETA /V /R:3 /W:3
robocopy . "%backupDest1%\_AlbumInfo\%backupDate%" *.* /XO /XD backup /S /E /ETA /V /R:3 /W:3
TITLE Updating Album File Locations...
FOR /F "tokens=1,2,3,4,5 delims=:" %%a in ('echo %backupDest1%') do SET myPicsUpdate=[%%a]%%b
FOR /F %%a in ('dir /b /a:d-h "%backupDest1%\_AlbumInfo\%backupDate%\"') do call :cmpAInfo "%backupDest1%\_AlbumInfo\%backupDate%\%%a"
TITLE Clearing My Pictures...
robocopy "%USERPROFILE%\My Documents\My Pictures" "%backupDest1%" /S /E /ETA /MOVE /R:3 /W:3
TITLE Backing up %backupDest1% to %backupDest2%...
robocopy "%backupDest1%" "%backupDest2%" /XO /S /E /ETA /V /R:3 /W:3
TITLE Update Active Albums...
robocopy "%backupDest1%\_AlbumInfo\%backupDate%" . *.* /XO /XD backup /S /E /ETA /V /R:3 /W:3
if NOT EXIST "%USERPROFILE%\My Documents\My Pictures" MKDIR "%USERPROFILE%\My Documents\My Pictures"
popd
goto :eof
pause
TITLE Command Prompt
goto :eof

:cmpAInfo
echo Processing %~1...
REM FOR /F %%a in ('dir /b "%~1"') do echo "%~1\%%a"
FOR /F %%a in ('dir /b /a:-d "%~1"') do gsar -i -o -s"$My Pictures" -r"%myPicsUpdate%" "%~1\%%a"
goto :eof

Now... you'll need to verify your date format at the command prompt, mine is DD/MM/YYYY the line that sets the backupDate may need to be tweaked. Of course the two destination variables need to be set appropriately for your own needs. Have fun... but play it safe test this for yourself first.

gsar.exe - Global Search and Replace
robocopy - Microsoft's Robocopy


Sunday, July 26, 2009

Tired of loading that HP Photo and Imaging Director?

You can eliminate the use of this middle-man tool by creating Desktop or Quick Launch toolbar shortcuts to the actual program easily, but it takes some research. The first step is to figure out what application is being run when you click on the functions of this HP dashboard, this is done by watching your Task Manager and watching for a new image name to appear under processes. This is most likely the executable's filename so search for the imagename (i.e. hpqkygrp.exe) to find out where this file lives.

You'll need to rename imagename.exe to something else, I'd suggest _magename.exe (_pqkygrp.exe as an example) and copy this program to the folder you found that imagename in and rename it to the original application's name.

Click on that HP Photo & Imaging Director button again, this will open a dialog with the complete command line, copy this command line and create a shortcut in either your Quick Launch toolbar, or on the desktop.

Repeat this process for each of the buttons in this launcer application. It may work for other applications too, but this was my own challenge and I figured I'd share it with you.

Remember to rename the program back to what it was called originally, and keep WinEcho.exe for the next time you need it.

Wednesday, April 01, 2009

PHP error_reporting Code/Cypher

When developing code, a web site, in PHP it's handy to turn on some of the error_reporting functionality during debugging. To determin the correct code(s) to use try this simple Google Docs spreadsheet.

The principle is best explained in the PHP manual (available online and only a Google away), but this may help you understand what's being enabled and disabled and with the code required.

Examples (Lifted from the manual for your understanding):
// Turn off all error reporting
error_reporting(0);

// Report simple running errors
error_reporting(E_ERROR | E_WARNING | E_PARSE);

// Reporting E_NOTICE can be good too (to report uninitialized
// variables or catch variable name misspellings ...)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);

// Report all errors except E_NOTICE
// This is the default value set in php.ini
error_reporting(E_ALL ^ E_NOTICE);

// Report all PHP errors
error_reporting(E_ALL);

// Same as error_reporting(E_ALL);
ini_set('error_reporting', E_ALL);

?>
PHP is one of the most powerful languages out there for web development, frankly there's not much it can't do when you add the right classes (which is like a DLL or Component on the ASP side) but the advantage is the cost... ZERO. If you want a development environment for your own personal machine, try out VirtualBox or VMWare Server and build one (Ubuntu or Ubuntu Server are great platforms, though OpenBSD, Debian, Fedora and the like are also popular and just as worthy). VMWare's VMTN has ready-to-use VMs you can simply plug in and fire up. I might pre-fab a couple of installations for VirtualBox over the next little while, no promises but if you're stuck let me know and I'll do what I can.

Thursday, July 03, 2008

TiddlyWiki - a reusable non-linear personal web notebook

This is NOT my technical trick, but definately worth a gander. Excellent work by Jeremy and the TiddlyWiki contributors!

TiddlyWiki - a reusable non-linear personal web notebook: "TiddlyWiki, a popular free MicroContent WikiWikiWeb created by JeremyRuston and a busy Community of independent developers. It's written in HTML, CSS and JavaScript to run on any modern browser without needing any ServerSide logic. It allows anyone to create personal SelfContained hypertext documents that can be posted to a WebServer, sent by email or kept on a USB thumb drive to make a WikiOnAStick."

Wednesday, March 19, 2008

Simplifying Rollouts: Server-Sensitive Code

I maintain a Linux/Apache/MySQL/PHP site as part of my role and keeping the sites sync and problem free between Development, Stage, and Production can be a little tricky if you've got things to worry about like outbound e-mails, scheduled jobs, and credit card clearing that you really do NOT want going awry when it hits production.

There are many method of detecting which manner of operation your various sites have, the simplest of which is a variable that states the mode in a common file. This one file can become a hazard because you need to remember not to sync that file to the production server.

I was looking for a manner by which to determine which server the code was executing on with a high-degree of reliability and this seems to work. I created the function called 'serverIPAddress()' which returns the obvious, but it's the how that is interesting. I must say, I am unsure whether this will work 100% for all of you as-is, as there many be slight differences in the output of the Linux command 'ifconfig' that need to be accounted for. In my implementation there are but two adapters, local (lo) and Ethernet (eth0), eth0 is listed first and I'm not sure this can change but I'll accept that it could. So, that said... the output of ifconfig looks like this:
eth0 Link encap:Ethernet HWaddr 00:0C:29:41:18:C4
inet addr:192.168.1.3 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe41:18c4/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:26995 errors:0 dropped:0 overruns:0 frame:0
TX packets:20748 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:3324252 (3.1 MiB) TX bytes:6947293 (6.6 MiB)
Interrupt:177 Base address:0x1400

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
The code to extract the 'inet addr' from this response looks like this:
function serverIpAddress(){
exec("/sbin/ifconfig",$ifconfig);
$target=explode(" ",$ifconfig[1]);
$bustoutip=explode(":",$target[5]);
$serverIP=$bustoutip[1];
return $serverIP;
}
The path for ifconfig is explicit due my use of this in a host of scheduled tasks (cron jobs) on the server. I'll be apply in this to my web site as well as the method for detection is based on 'SERVER_NAME' and a little less reliable/safe in my books.

Once I have the IP Address I can move on to making server specific code that understands what to do dependant upon the server that it is executing on. The following code takes the resulting IP Address and sets variables, etc. to ensure that code transitioned from development to stage/production can understand and behave reliably. There's nothing worse during service recovery when there are code differences, especially if the code differences are not well documented.
switch(serverIPAddress()){
case '192.168.1.3': // PRODUCTION
$enable_say = 1;
define( "DEBUG", false);
$SMTPSmartHost = "192.168.1.7"; // Kerio Server
define("K_ServerMode","P");
break;
case '192.168.1.9': // STAGE
$enable_say = 1;
define( "DEBUG", false); //DEV
$SMTPSmartHost = "192.168.1.2"; // Cameron's DEV VM
define("K_ServerMode","S");
break;
case '192.168.1.45': // DEV
default:
$enable_say = 1;
define( "DEBUG", true); //DEV
$SMTPSmartHost = "192.168.1.2"; // Cameron's DEV VM
define("K_ServerMode","D");
break;
}

There is no individual ownership when you are part of a team, it's the sum of the parts that makes you the RESILIENT team you need to be...