Thursday, October 30, 2008

All Your Command (CMD) Prompts Are Belong To Us

Ever typed SET to display all the useful environment variables windows has?

You can add those environment variables to your command prompt permanently. And No, Billy, this won't work by simply editing System Properties, so read on...

Four files and the PSEXEC.EXE utility are needed - save all 4 files in the same directory in a share(such as \\server\share ) to prepare to deploy your groovy command prompt on every server under your influence.

I changed the command prompt to better keep track of which server and which credentials are in use, and added a timestamp.

CMDPROMPT.BAT - contains the prompt with your preferred environment variables and gets copied to %windir%
:: c:\windows\cmdprompt.bat
@echo off
prompt $_[%ComputerName%\%Username%] $t$_$p$g

CMDPROMPT.REG - get registered on the target machine(s) to invoke CMDPROMPT.BAT when CMD.EXE is executed
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor]
"AutoRun"="%windir%\\cmdprompt.bat"


CMDPROMPT-INSTALL.CMD - the installer script - important for deploying the prompt to multiple machines
:: \\server\share\cmdprompt-install.cmd
@echo off
copy /Y \\server\share\cmdprompt.??? %windir%\
regedit /s %windir%\cmdprompt.reg


SERVERLIST.TXT - a list of target machines(one per line) that will receive the newly minted command prompt goodness.
Eriador
Gondor
GreyHavens
Minastirith
Mordor
Rivendell
Rohan
Bullwinkle
Rocky
Elaine
George
Jerry
Challenger
Cubs
Hindenburg
SpruceGoose
Titanic
TowerofPiza

Now to install! Open up a command prompt and cd to the directory containing the SERVERLIST.TXT , then run the following:
psexec @SERVERLIST.TXT -c -u DOMAIN\%username% \\server\share\cmdprompt-install.cmd


Login to some of these target servers and enjoy the utility of the improved prompt.

Tuesday, October 28, 2008

sp_helpprotect2 - Version of sp_helpprotect stored proc that Returns Valid T-sql Permissions

One common task with SQL Server is viewing and modifying table permissions.

sp_helprotect can spit out some very helpful reports, but the returned SQL does not comprise valid T-SQL statements unless the version of sp_helprotect in the master database is modified a bit. This modified version is called sp_helprotect2, and marks itself as a system object. Thanks to wzard on Experts Exchange for this syntax.


/****** Object: StoredProcedure [dbo].[sp_helprotect2]
by: wzard http://www.experts-exchange.com/Database/Miscellaneous/Q_21694613.html

Edited and Reposted by Lars Rasmussen on http://larsrasmussen.blogspot.com/
******/

SET
NOCOUNT ON
GO


PRINT
'Using Master database'
USE
master
GO

PRINT
'Checking if procedure already exists in master database...'
IF
(SELECT OBJECT_ID('sp_helprotect2','P')) IS NOT NULL --means, the procedure already exists
BEGIN
PRINT 'Procedure already exists - dropping it.'
DROP
PROC sp_helprotect2
END
GO


CREATE
PROCEDURE [dbo].[sp_helprotect2]
@name ncharacter varying(776) = NULL
,
@username sysname = NULL
,
@grantorname sysname = NULL
,
@permissionarea character varying(10) = 'o s'
as
BEGIN

/********
Explanation of the parms...
---------------------------
@name: Name of [Owner.]Object and Statement; meaning
for sysprotects.id and sysprotects.action at the
same time; thus see parm @permissionarea.
Examples- 'user2.tb' , 'CREATE TABLE', null

@username: Name of the grantee (for sysprotects.uid).
Examples- 'user2', null

@grantorname: Name of the grantor (for sysprotects.grantor).
Examples- 'user2' --Would prevent report rows which would
-- have 'dbo' as grantor.

@permissionarea: O=Object, S=Statement; include all which apply.
Examples- 'o' , ',s' , 'os' , 'so' , 's o' , 's,o'
GeneMi
********/
Set nocount on
Declare
@vc1 sysname
,
@Int1 integer
Declare
@charMaxLenOwner character varying(11)
,
@charMaxLenObject character varying(11)
,
@charMaxLenGrantee character varying(11)
,
@charMaxLenGrantor character varying(11)
,
@charMaxLenAction character varying(11)
,
@charMaxLenColumnName character varying(11)
Declare
@OwnerName sysname
,
@ObjectStatementName sysname

/* Perform temp table DDL here to minimize compilation costs*/
CREATE Table #t1_Prots
( Id int Null
,Type1Code char(6) collate database_default NOT Null
,
ObjType char(2) collate database_default Null
,ActionName varchar(20) collate database_default Null
,
ActionCategory char(2) collate database_default Null
,
ProtectTypeName char(10) collate database_default Null
,Columns_Orig varbinary(32) Null
,OwnerName sysname collate database_default NOT Null
,
ObjectName sysname collate database_default NOT Null
,
GranteeName sysname collate database_default NOT Null
,
GrantorName sysname collate database_default NOT Null
,ColumnName sysname collate database_default Null
,
ColId smallint Null
,Max_ColId smallint Null
,
All_Col_Bits_On tinyint Null
,
new_Bit_On tinyint Null ) -- 1=yes on

/* Check for valid @permissionarea */
Select
@permissionarea = upper( isnull(@permissionarea,'?') )
IF ( charindex('O',@permissionarea) <= 0
AND charindex('S',@permissionarea) <= 0)
begin
raiserror(15300,-1,-1 ,@permissionarea,'o,s')
return
(1)
end
select @vc1 = parsename(@name,3)
/* Verified db qualifier is current db*/
IF
(@vc1 is not null and @vc1 <> db_name())
begin
raiserror(15302,-1,-1) --Do not qualify with DB name.
return
(1)
end
/* Derive OwnerName and @ObjectStatementName*/
select
@OwnerName = parsename(@name, 2)
,@ObjectStatementName = parsename(@name, 1)
IF (@ObjectStatementName is NULL and @name is not null)
begin
raiserror(15253,-1,-1,@name)
return
(1)
end
/* Copy info from sysprotects for processing */
IF
charindex('O',@permissionarea) > 0
begin
/* Copy info for objects */
INSERT
#t1_Prots
(
Id
,Type1Code
,ObjType
,
ActionName
,
ActionCategory
,
ProtectTypeName
,Columns_Orig
,
OwnerName
,
ObjectName
,
GranteeName
,GrantorName
,
ColumnName
,
ColId
,Max_ColId
,
All_Col_Bits_On
,
new_Bit_On )
/* 1Regul indicates action can be at column level,
2Simpl indicates action is at the object level */
SELECT
id
,case
when columns is null then '2Simpl'
else
'1Regul'
end
,Null
,
val1.name
,
'Ob'
,
val2.name
,columns
,
user_name(objectproperty( id, 'ownerid' ))
,
object_name(id)
,
user_name(uid)
,user_name(grantor)
,
case
when columns is null then '.'
else
Null
end
,-
123
,Null
,Null

,Null
FROM sysprotects sysp
,master.dbo.spt_values val1
,
master.dbo.spt_values val2
where (@OwnerName is null or user_name(objectproperty( id, 'ownerid' )) = @OwnerName)
and
(@ObjectStatementName is null or object_name(id) = @ObjectStatementName)
and
(@username is null or user_name(uid) = @username)
and
(@grantorname is null or user_name(grantor) = @grantorname)
and
val1.type = 'T'
and
val1.number = sysp.action
and
val2.type = 'T' --T is overloaded.
and
val2.number = sysp.protecttype
and
sysp.id != 0

IF EXISTS (SELECT * From #t1_Prots)
begin
UPDATE #t1_Prots set ObjType = ob.xtype
FROM
sysobjects ob
WHERE
ob.id = #t1_Prots.Id

UPDATE #t1_Prots
set
Max_ColId = (select max(colid) from syscolumns sysc
where #t1_Prots.Id = sysc.id) -- colid may not consecutive if column dropped
where Type1Code = '1Regul'

/* First bit set indicates actions pretains to new columns. (i.e. table-level permission)
Set new_Bit_On accordinglly */
UPDATE #t1_Prots SET new_Bit_On =
CASE
convert(int,substring(Columns_Orig,1,1)) & 1
WHEN 1 then 1
ELSE
0
END
WHERE
ObjType <> 'V' and Type1Code = '1Regul'

/* Views don't get new columns */
UPDATE
#t1_Prots set new_Bit_On = 0
WHERE
ObjType = 'V'

/* Indicate enties where column level action pretains to all
columns in table All_Col_Bits_On = 1 */
UPDATE #t1_Prots set All_Col_Bits_On = 1
where
#t1_Prots.Type1Code = '1Regul'
and
not exists
(select *
from
syscolumns sysc, master..spt_values v
where
#t1_Prots.Id = sysc.id and sysc.colid = v.number
and
v.number <= Max_ColId -- column may be dropped/added after Max_ColId snap-shot
and
v.type = 'P' and
/* Columns_Orig where first byte is 1 means off means on and on mean off
where first byte is 0 means off means off and on mean on */
case convert(int,substring(#t1_Prots.Columns_Orig, 1, 1)) & 1
when 0 then convert(tinyint, substring(#t1_Prots.Columns_Orig, v.low, 1))
else
(~convert(tinyint, isnull(substring(#t1_Prots.Columns_Orig, v.low, 1),0)))
end & v.high = 0)

/* Indicate entries where column level action pretains to
only some of columns in table All_Col_Bits_On = 0*/
UPDATE #t1_Prots set All_Col_Bits_On = 0
WHERE
#t1_Prots.Type1Code = '1Regul'
and
All_Col_Bits_On is null

Update #t1_Prots
set
ColumnName =
case
when All_Col_Bits_On = 1 and new_Bit_On = 1 then '(All+New)'
when
All_Col_Bits_On = 1 and new_Bit_On = 0 then '(All)'
when
All_Col_Bits_On = 0 and new_Bit_On = 1 then '(New)'
end
from
#t1_Prots
where
ObjType IN ('S ' ,'U ', 'V ')
and
Type1Code = '1Regul'
and
NOT (All_Col_Bits_On = 0 and new_Bit_On = 0)

/* Expand and Insert individual column permission rows */
INSERT
into #t1_Prots
(Id
,
Type1Code
,
ObjType
,
ActionName
,ActionCategory
,
ProtectTypeName
,
OwnerName
,
ObjectName
,GranteeName
,
GrantorName
,
ColumnName
,
ColId )
SELECT prot1.Id
,'1Regul'
,
ObjType
,
ActionName
,ActionCategory
,
ProtectTypeName
,
OwnerName
,
ObjectName
,GranteeName
,
GrantorName
,
col_name ( prot1.Id ,val1.number )
,
val1.number
from #t1_Prots prot1
,master.dbo.spt_values val1
,
syscolumns sysc
where prot1.ObjType IN ('S ' ,'U ' ,'V ')
and prot1.All_Col_Bits_On = 0
and
prot1.Id = sysc.id
and
val1.type = 'P'
and
val1.number = sysc.colid
and

case
convert(int,substring(prot1.Columns_Orig, 1, 1)) & 1
when 0 then convert(tinyint, substring(prot1.Columns_Orig, val1.low, 1))
else
(~convert(tinyint, isnull(substring(prot1.Columns_Orig, val1.low, 1),0)))
end & val1.high <> 0
delete from #t1_Prots
where ObjType IN ('S ' ,'U ' ,'V ')
and All_Col_Bits_On = 0
and
new_Bit_On = 0
end
end

/* Handle statement permissions here*/
IF
(charindex('S',@permissionarea) > 0)
begin

/* All statement permissions are 2Simpl */
INSERT #t1_Prots
( Id
,Type1Code
,
ObjType
,
ActionName
,ActionCategory
,
ProtectTypeName
,
Columns_Orig
,
OwnerName
,ObjectName
,
GranteeName
,
GrantorName
,
ColumnName
,ColId
,
Max_ColId
,
All_Col_Bits_On
,
new_Bit_On )
SELECT id
,'2Simpl'
,Null

,
val1.name
,'St'
,
val2.name
,
columns
,
'.'
,'.'
,
user_name(sysp.uid)
,
user_name(sysp.grantor)
,
'.'
,-
123
,Null
,Null

,Null
FROM sysprotects sysp
,master.dbo.spt_values val1
,
master.dbo.spt_values val2
where (@username is null or user_name(sysp.uid) = @username)
and (@grantorname is null or user_name(sysp.grantor) = @grantorname)
and
val1.type = 'T'
and
val1.number = sysp.action
and
(@ObjectStatementName is null or val1.name = @ObjectStatementName)
and
val2.number = sysp.protecttype
and
val2.type = 'T'
and
sysp.id = 0
end

IF NOT EXISTS (SELECT * From #t1_Prots)
begin
raiserror(15330,-1,-1)
return
(1)
end

/* Calculate dynamic display col widths */
SELECT

@charMaxLenOwner =
convert ( varchar, max(datalength(OwnerName)))
,@charMaxLenObject =
convert ( varchar, max(datalength(ObjectName)))
,@charMaxLenGrantee =
convert ( varchar, max(datalength(GranteeName)))
,@charMaxLenGrantor =
convert ( varchar, max(datalength(GrantorName)))
,@charMaxLenAction =
convert ( varchar, max(datalength(ActionName)))
,@charMaxLenColumnName =
convert ( varchar, max(datalength(ColumnName)))
from #t1_Prots


/* Output the report */

create
table #mytmp ([owner] varchar(255), [object] varchar(255), grantee varchar(255), grantor varchar(255), protecttype varchar(255), [action] varchar(255), [column] varchar(255))
EXECUTE
(
'Set nocount off
INSERT INTO #mytmp
SELECT ''Owner'' = substring (OwnerName ,1 ,'
+ @charMaxLenOwner + ')
,''Object'' = substring (ObjectName ,1 ,' + @charMaxLenObject + ')
,''Grantee'' = substring (GranteeName ,1 ,' + @charMaxLenGrantee + ')
,''Grantor'' = substring (GrantorName ,1 ,' + @charMaxLenGrantor + ')
,''ProtectType''= ProtectTypeName
,''Action'' = substring (ActionName ,1 ,' + @charMaxLenAction + ')
,''Column'' = substring (ColumnName ,1 ,' + @charMaxLenColumnName + ')
from #t1_Prots
order by
ActionCategory
,Owner ,Object
,Grantee ,Grantor
,ProtectType ,Action
,ColId --Multiple -123s ( &lt0 ) possible

Set nocount on'

)

select
[protecttype] + ' ' + [action] + ' on [' + [object] + '] to [' + [grantee] + ']' from [#mytmp]
Return
(0) -- sp_helprotect2
END

GO

PRINT
'Procedure created.'
GO

--Mark procedure as system object

EXEC
sp_MS_marksystemobject sp_helprotect2
GO

Tuesday, October 14, 2008

Create Windows Shares from the Command Line using RMTSHARE.EXE

RMTSHARE.EXE from one of the Windows NT Resource kits makes quick work of creating shares.

Here's an example of creating a hidden, read-only share accessible only by the fictional AD group 'DOMAIN\AdminsSQL':
rmtshare \\SQLSERVERNAME\BACKUPS-RO$=E:\BACKUPS /GRANT "DOMAIN\AdminsSQL":r


Want write(change) access but still with a hidden share? Use this syntax:
rmtshare \\SQLSERVERNAME\BACKUPS-RW$=E:\BACKUPS /GRANT "DOMAIN\AdminsSQL":c


Remove the dollar sign from the end of the share name if you want the share to be visible.

Running RMTSHARE.EXE against a \\SERVERNAME with no parameters shows all the shares(including the hidden shares) available, like so:
C:\>rmtshare \\SQLSERVERNAME

Share name Resource Remark

-------------------------------------------------------------------------------
IPC$ Remote IPC
C$ C:\ Default share
X$ X:\ Default share
ADMIN$ C:\WINDOWS Remote Admin
BACKUPS-RO$ E:\BACKUPS Shared by remote command.
BACKUPS-RW$ E:\BACKUPS Shared by remote command.
D$ D:\ Default share
L$ L:\ Default share
E$ E:\ Default share
The command completed successfully.


Tested in Vista, Server 2003, Server 2000. Let me know if you find a version of Windows incompatible with RMTSHARE.EXE!

Friday, October 10, 2008

'Copy as Path' - Vista's Best Feature

Arguably one of the best features in Vista for those who live in Explorer with files and directories appears by holding down the Shift key when right-clicking a file or folder:



Copy as Path copies the full path of the file or folder selected as text to the clipboard that can then be pasted in email, in code or at the command line.

The full path gets copied as a plain text string, surrounded by quotes to handle any spaces that might be contained in the path/filename.

Tuesday, October 07, 2008

Move TEMPDB to a Separate Drive

It can be beneficial to move TEMPDB to an alternate location for improved performance. I've run across installations where tempdb was located on the OS drive with autogrow enabled and the drive nearly ran out of space!

This change requires a restart of the SQL Server Service(MSSQLSERVER).

Here's an example in SQL Server 2005 of moving TEMPDB to the (T:) drive with one data file per CPU(four CPUs). On the box used in this example, the (T:) drive is on a SAN.

SELECT name, physical_name
FROM
sys.master_files
WHERE
database_id = DB_ID('tempdb');
GO

ALTER
DATABASE tempdb
MODIFY
FILE (NAME = tempdev, FILENAME = 'T:\SQLTEMPDB\tempdb.mdf');
GO
ALTER
DATABASE tempdb
MODIFY
FILE (NAME = templog, FILENAME = 'T:\SQLTEMPDB\templog.ldf');
GO

ALTER
DATABASE [tempdb] ADD FILE ( NAME = N'tempdb1', FILENAME = N'T:\SQLTEMPDB\tempdb1.ndf' , SIZE = 100MB , FILEGROWTH = 50MB )
GO
ALTER
DATABASE [tempdb] ADD FILE ( NAME = N'tempdb2', FILENAME = N'T:\SQLTEMPDB\tempdb2.ndf' , SIZE = 100MB , FILEGROWTH = 50MB )
GO
ALTER
DATABASE [tempdb] ADD FILE ( NAME = N'tempdb3', FILENAME = N'T:\SQLTEMPDB\tempdb3.ndf' , SIZE = 100MB , FILEGROWTH = 50MB )
GO
ALTER
DATABASE [tempdb] MODIFY FILE ( NAME = N'tempdev', SIZE = 100MB , FILEGROWTH = 50MB )
GO
ALTER
DATABASE [tempdb] MODIFY FILE ( NAME = N'templog', SIZE = 10MB , FILEGROWTH = 10MB )
GO

SELECT
name, physical_name
FROM
sys.master_files
WHERE
database_id = DB_ID('tempdb');
GO

--restart needed to affect changes

Saturday, August 16, 2008

Amazon MP3 Denies Consumer Access to Previously Purchased Content

Download purchased MP3s from Amazon more than one time? Not without intervention from tech support.

I began trying Amazon's MP3 download service recently, and was initially pleased with the results. After purchasing a track I could visit the 'Your Media Library' section of the site and download my previous purchases, even if I was at another computer.

Not anymore.

Amazon will let me pay for the same track twice(albeit with a recently added warning), but there is no longer an option on the site to "redownload" tracks already purchased, _unless_ you contact Amazon's technical support. The following video screenshot displays the warning without any option to download content previously purchased.



This is further complicated by a lacking website implementation of the Amazon MP3 Downloader application, which only works if the Amazon MP3 site sends an 'AMZ' file to download cover art and create Artist\Album folder directory trees. If the site sends you an MP3 file instead of an AMZ, you get the pleasure of creating your Artist\Album folder directory trees manually!

Should you choose to cancel the download after purchasing, the site will not let you try to download the track again unless Customer support triggers a magic bit. And should you receive an MP3 file download prompt instead of an AMZ file that works with the Amazon MP3 Downloader, the whole cycle repeats itself.

At the very least Amazon could allow users an option to default to downloading AMZ files instead of a manual MP3 download.

Allowing users to choose to download AMZ files would have prevented me from attempting to download files to the same computer twice. But there was not a choice. After initiaing a purchase, the MP3 download was initiated, and that was it. I tried leaving the Amazon MP3 Downloader running, reinstalling it, and modifying the browser settings to force AMZ files to open in the Amazon MP3 Downloader instead of the default associated AMZ app(which was already the Amazon MP3 Downloader). Again, if an .MP3 download is already started by the Amazon web site, it's too late to try a second time to get the AMZ file to launch.

This also means accessing your purchased tracks on another computer is no longer a feature. It's been recently yanked.

Amazon was pretty quick to respond to my inquiry, but could they do more?
Here's the text of their response:

Hello from Amazon.com.

Amazon MP3 files are only available for download at the time of purchase. We display information about the items you've purchased from the Amazon MP3 Music Downloads store in Your Media Library so you can keep track of what you've bought and discover similar items that may be available. However, the files are not stored there and cannot be downloaded again after purchase without intervention from Amazon MP3 Support on an exceptional basis. This has always been the case with our downloads.

Because you can only download an MP3 file once upon purchase, we encourage you to make back-up copies of your MP3 Music files to ensure you will always have access.
[info about browser/cookie config removed]

Amazon claims this functionality has never been available. Methinks it has been removed in the last month, because I was able to download tracks more than once that I had purchased via the 'Your Media Library' without contacting support.

Amazon could do more to ensure that their customers receive the digital media purchased. Amazon's customer service has been handled well in other areas... why put the burden on the consumer, when the site can allow multiple downloads of previously purchased content?

Is this a digital iteration of forcing consumers to buy media more than once, similar to moves from VHS to DVD to Blu-Ray?

Nope. It's worse. In this case the consumer's second purchase does not include any additional value. The burdens of data integrity and recoverability are placed upon the consumer. Don't be concerned, because Amazon has a service that can be used for backups, too.

Friday, July 18, 2008

Querying Disk Space on Remote Servers using Batch with WMIC

Time to check the disk free space in GB and percentage on a server volume...

WMIC(Windows Management Instrumentation Command-line) makes another appearance!

Thanks to Tipsmark for this syntax (Response number 17 in this post). I added the /node switch and some error handling / usage to have this batch file work on remote machines.

@ECHO OFF
IF "%~1"=="" goto help
IF "%~2"=="" goto help

@SETLOCAL ENABLEEXTENSIONS
@SETLOCAL ENABLEDELAYEDEXPANSION

@FOR /F "tokens=1-3" %%n IN ('"WMIC /node:"%1" LOGICALDISK GET Name,Size,FreeSpace | find /i "%2""') DO @SET FreeBytes=%%n & @SET TotalBytes=%%p

@SET /A TotalSpace=!TotalBytes:~0,-9!
@SET /A FreeSpace=!FreeBytes:~0,-10!
@SET /A TotalUsed=%TotalSpace% - %FreeSpace%
@SET /A PercentUsed=(!TotalUsed!*100)/!TotalSpace!
@SET /A PercentFree=100-!PercentUsed!

IF %TotalSpace% LSS 0 goto error

@ECHO Total space: %TotalSpace%GB
@ECHO Free space: %FreeSpace%GB
@ECHO Used space: %TotalUsed%GB
@ECHO Percent Used: %PercentUsed%%%
@ECHO Percent Free: %PercentFree%%%

@SET TotalSpace=
@SET FreeSpace=
@SET TotalUsed=
@SET PercentUsed=
@SET PercentFree=
goto end

:error
echo.
echo *** Invalid server or drive specified ***
echo.
goto help

:help
echo.
echo diskfree.cmd
echo.
echo Queries remote server for free disk space.
echo Specify a MACHINENAME and a drive letter to be queried
echo.
echo Example: diskfree.cmd MACHINENAME c:
echo.
goto end


:end


Here's an example of the script being run with a target computer named 'LARS', checking for free space on the [F:] volume:





If parameters are not passed or passed incorrectly(wrong drive letter) the script outputs the following or similar:

Thursday, June 05, 2008

Remotely Remove IIS via the Command Line Using PsExec and Sysocmgr.exe

IIS components can be manually removed the with the Add/Remove Programs>>Add/Remove Windows Components tool in Control Panel, or they can be uninstalled from the command line with a little script Kung-Fu. Several Windows components can be added/removed using this same technique by modifying the answer file.

Here's the syntax:

psexec \\TARGETMACHINENAME –u DOMAINNAME\%username% sysocmgr /i:%windir%\inf\sysoc.inf /u:"\\path\to\share\IIS_REMOVE-unattended.txt" /r /q
Here are the contents of the IIS_REMOVE-unattended.txt answer file - I've chosen to remove only 3 relevant components of IIS:
[Components]
iis_common = OFF
iis_www = OFF
iis_inetmgr = OFF

Here's what execution looks like:


Running
sysocmgr /?
from Start>>Run will display the available parameters for the tool.

Thursday, May 15, 2008

50.7 cents per GB - WD External 2.5" USB-Powered Drive - WDMLZ3200CN

The going rate for consumer storage is $0.20/GB or less you say? Why pay so much?



Ah, but that's in 3.5" internal SATA drives. Once you're able to fit 320GB(298GB as reported by Windows) in a coat pocket without needing an additional bulky power adapter to lug around, you'll see the instant appeal of such a device.

Make sure you get one with a carrying case - which is to say - you'll likely buy the Western Digital My Passport™ Elite™ from Costco.

Starting today there is $30 off coupon in the Costco mailer on a 2.5" external 320GB drive(298GB as reported by Windows OS) - I spent $151.12 including tax today for mine. Comes with a carrying case and an LED capacity gauge on the side that lights up to indicate remaining capacity - 320gb of portable goodness.

You can sometimes ask the Customer Service desk at the warehouse for coupons if you don't have the mailer.

Otherwise you'll pay right around $160.95 to purchase online from Costco.

The device comes formatted as FAT32, which doesn't support files larger tha 4GB(2 32−1 bytes to be more precise), so format it with NTFS - that'll work with the latest *NIX & Windows OSes.

Disclaimer: I like Costco quite a bit.

Monday, April 07, 2008

Using Floola and Media Monkey with the iPod Nano(iTunes Fails to Grant Joy)

I purchased a 3rd generation 8GB Nano for $187 & change(not including tax) at my local wholesale warehouse(Costco). I was excited to load several files on to the device for my wife so that she could immediately begin to use it for her birthday. I spoke with a friend who already had the files(hymns and religious talks freely available for personal use from LDS.ORG) loaded on his iPod, and I naively suggested that we copy the files from his device(an older iPod) to the newly purchased gift for my wife. My buddy grinned and explained why copying files from one iPod to another might be difficult and/or undesirable due to limitations of the Apple iTunes software. I understood that Apple would want to maintain a tight grip on their DRM business model, but files that are not distributed by Apple shouldn't be subject to these Apple restrictions, as the use of said files is governed by the author or copyright holder, in this case a company called Intellectual Reserve.

Even though the copyright holder had specifically designated the files as freely copyable and redistributable for personal use, iTunes limits the number of times files may be copied from one device to another, or from a library to a device.

I ended up using a program called Floola to copy the files from my buddy's Mac Mini(not directly from his iPod) to my wife's Nano. Floola is multi-platform and can run directly from an iPod, so I copied the Windows(a standalone executable FLOOLA.EXE) and the Mac(a DMG file) versions to the Nano.

Steps:
  1. Installed iTunes 7.4.3 from http://oldversion.com to my wife's PC and copied an album in MP3 format I had previously purchased from allofmp3.com to the iPod Nano using iTunes.
  2. Copied Mac & Windows versions of Floola to the iPod Nano & tested adding/deleting tracks - worked fine.
  3. Uninstalled iTunes from wife's PC.
  4. Went to Buddy's house, plugged in wife's iPod Nano and launched OS X version of Floola directly from the Nano.
  5. Used Floola to copy files from buddy's Mac Mini hard drive to wife's iPod Nano.
  6. Closed Floola, Disconnected Nano from buddy's Mac Mini.
  7. Verified that the files now existed on wife's iPod Nano.
  8. Returned home.
  9. Installed Media Monkey on Wife's PC as a replacement for iTunes, showed my wife how to use it in 20 minutes.


While the iTunes interface seems simple to use I really don't see the point in forcing Apple licensing limitations to media I already have the right to use. In my mind iTunes is crippleware because it restricts the playback, management, and transfer of media I already have the right to use and redistribute.

Additional Cons/gripes against the iPod Nano:
  • $187 for only 8GB of capacity seems too expensive - that's less than 2 DVD-Rs of storage.
  • The included USB sync cable is very thin - since the cable is used for charging(power) as well as data transfer and lacks additional shielding the transfer rate is likely compromised.
  • Is my sweetie really gonna watch video on this tiny screen? Rarely, if ever. A monochrome screen with 32GB of storage capacity would be much preferred, allowing for storage of many more audio files.
  • Having to pay $30 to install an after market protective cover(InvisibleShield) because the device so easily gets scratched.

Pros:
  • Small & light, bright display with good contrast.
  • The wheel on the Nano provides a smooth interface. I wonder how often it'll get used by my wife since she's using playlists, though. It's a simple matter to find a playlist on any media player and hit play.