Friday, August 17, 2012

EXIF Extraction

So, like any good programmer, I have the camel on my bookshelf where the allmighty Larry asserts (p. 609) that laziness is the first great virtue of a programmer.  Not to be a virtue-less programmer, I was off the teh googlez to search for an EXIF library.

I figured that it's bound to have come up before and lo and behold I found many a library.  Most did pretty much what I wanted, but this is for a web app and every single library left a lock on the file after it was done executing.  I guess I could have restarted the app pool after every page load, but that seemed a bit excessive.

I poked around MSDN and saw that it wasn't that difficult to read it by myself.

So, I wrote my own chunk of code:

b = new System.Drawing.Bitmap(fileInfo.FullName)
  Dim encoding As New System.Text.ASCIIEncoding()
  for each pi in b.PropertyItems
    if pi.ID = 40091 then ' I only need the title
      description = System.Text.Encoding.Unicode.GetString(pi.value)
      exit for
    end if

and viola, EXIF data.

Sidebar here:  I am a c# guy.  I cannot stand using vb.  but, DotNetNuke is written in vb so, I don't have much of a choice (I'm not rolling a new class for 15 lines of code)

But, with the EXIF data came the aforementioned file lock.

Back to MSDN to poke around at the Image Class.  Skip down a bit and lookie lookie it implements IDisposible.  Oh boy, is it really this easy?

yes, in fact it is.

using b = new System.Drawing.Bitmap(fileInfo.FullName)
  Dim encoding As New System.Text.ASCIIEncoding()
  for each pi in b.PropertyItems
    if pi.ID = 40091 then
      description = System.Text.Encoding.Unicode.GetString(pi.value)
      exit for
    end if
End Using

Game, set, match.

Friday, February 17, 2012

Backup Script (2.0)

So, as promised, I am posting my new backupscript.  I've completely reworked it.  You know call it with a specific database, backup type (log, full, or differential), and a backup path.

Here's the basic logic:

If we try to backup tempdb, bail (You can't back up tempdb)

If you try to backup master with differential or log, bail

If you try to take a log backup of a non full recovery db, bail
  (I dont have any bulk logged DBs, but I probably should change this)

Get the DB Guid (This was for databases that I restored with the same name, but were different databases altogether.  I using the Database Name to query the history.  the GUID is much more robust.

Force the BackupPathRoot to have a trailing \  - So the path works properly.

Get the last full backup information

If there was never a full backup, switch the backup type to full

If the last full is not in the backup path root, or is missing, switch to full (I'm thinking about changing this to only happen if we're taking a differential)

If we're taking a log backup, Check the log chain (just need to check from the last full or the last differential)

In the backup path root, make a subfolder for the database if it doesn't exist.

Backup the database with the following format:  %BackupPathRoot%\%ServerName%\%DatabaseName%\%DatabaseName% YYYYMMDD HHMMSS %BackupType%.bak

so, there ya go...

here's the link : BackupScript

Here's the source:

DROP PROC BackupDatabase
CREATE PROC BackupDatabase
@database SYSNAME,
@backuptype VARCHAR(20),
--Test Data
  @database sysname,
  @backuptype varchar(20),
  @backupPathRoot varchar(255)

set @database = 'robtest'
set @backuptype = 'log'
set @backupPathRoot='C:\Backup'
@debugmessages INT,
@DifferentialBackupLocation NVARCHAR(260),
@DifferentialBackupSetId INT,
@FileExists INT,
@filename VARCHAR(255),
@folder VARCHAR(255),
@FullBackupLocation NVARCHAR(260),
@FullBackupSetId INT,
@LogMessage VARCHAR(MAX),
@BackupLogID INT
--select @BackupLogID=MAX(id) from BackupLog -- Just for TestingSET @debugmessages = 0-- Dont Back up TempDBIF (@database = 'TempDB') RETURN-- Only Full backups for MasterIF (@database = 'Master' AND @backuptype <> 'Full') RETURN-- Don't take Log backups when the database isn't in full recoveryIF (@backuptype = 'Log' AND EXISTS (SELECT 1 FROM sys.databases WHERE recovery_model_desc <> 'FULL' AND name=@database))
RETURN-- Get the Database GuidSELECT @databaseGuid = database_guid FROM sys.database_recovery_status WHERE database_id = DB_ID(@database)-- Make sure the @backupPathRoot has a trailing \IF (@backupPathRoot NOT LIKE '%\') SET @backupPathRoot = @backupPathRoot + '\' -- Get the last full backup location and idSELECT @FullBackupLocation=physical_device_name, @FullBackupSetId=backup_set_idFROM msdb.dbo.backupmediafamily fJOIN msdb.dbo.backupset s ON f.media_set_id=s.media_set_idWHERE backup_set_id=(
SELECT MAX(msdb.dbo.backupset.backup_set_id)
FROM msdb.dbo.backupset
WHERE msdb.dbo.backupset.TYPE = 'D'
AND database_name=@database
AND database_guid=@databaseGuid )
IF (@FullBackupLocation IS NULL) BEGIN SET @backuptype='Full'  EXEC BackupLogEntry 'BackupDatabase', @database, 'No Full backup exists' END
( @FullBackupLocation NOT LIKE @backupPathRoot + '%') BEGIN SET @backuptype='Full' EXEC BackupLogEntry 'BackupDatabase', @database, 'Full backup not in the correct folder' END
..xp_fileexist @FullBackupLocation, @FileExists output
IF (@FileExists = 0) BEGIN
SET @LogMessage = 'Full Backup Location (' + @FullBackupLocation + ') missing from the folder'
EXEC BackupLogEntry 'BackupDatabase', @database, @LogMessage
(@debugmessages=1) EXEC BackupLogEntry 'BackupDatabase', @database, 'Full backup exists'
-- Check to make sure the files are in the correct locationIF (@backuptype = 'Log') BEGIN

--Get differential backup location
SELECT @DifferentialBackupLocation=physical_device_name, @DifferentialBackupSetId=backup_set_id
FROM msdb.dbo.backupmediafamily f
JOIN msdb.dbo.backupset s ON f.media_set_id=s.media_set_id
WHERE backup_set_id=(
SELECT MAX(msdb.dbo.backupset.backup_set_id)
FROM msdb.dbo.backupset
WHERE msdb.dbo.backupset.TYPE = 'I'
AND backup_set_id > @FullBackupSetId
AND database_name=@database
AND database_guid=@databaseGuid

-- Check differential backup location
IF (@DifferentialBackupLocation IS NOT NULL) BEGIN
(@debugmessages=1) BEGIN SET @LogMessage = 'Checking Differential Backup Location (' + @DifferentialBackupLocation + ')' EXEC BackupLogEntry 'BackupDatabase', @database, @LogMessage END
( @DifferentialBackupLocation NOT LIKE @backupPathRoot + '%' ) BEGIN
@LogMessage = 'Differential Backup (' + @DifferentialBackupLocation + ') in wrong folder'
EXEC BackupLogEntry 'BackupDatabase', @database, @LogMessage
SET @DifferentialBackupLocation = NULL
..xp_fileexist @DifferentialBackupLocation, @FileExists output
IF (@FileExists = 0) BEGIN
@LogMessage = 'Differential Backup (' + @DifferentialBackupLocation + ') missing'
EXEC BackupLogEntry 'BackupDatabase', @database, @LogMessage
SET @DifferentialBackupsetId = NULL
(@debugmessages=1) EXEC BackupLogEntry 'BackupDatabase', @database, 'Differential backup found'

(@debugmessages=1) EXEC BackupLogEntry 'BackupDatabase', @database, 'Checking Log Chain'

FROM msdb.dbo.backupmediafamily f
JOIN msdb.dbo.backupset s ON f.media_set_id=s.media_set_id
WHERE backup_set_id > COALESCE(@DifferentialBackupsetId, @fullbackupsetid)
AND database_guid=@databaseGuid

OPEN cur
FETCH next FROM cur INTO @filename

WHILE @backuptype='log' AND @@FETCH_STATUS=0 BEGIN
(@debugmessages=1) BEGIN SET @LogMessage = 'Checking Log Backup Location (' + @filename + ')' EXEC BackupLogEntry 'BackupDatabase', @database, @LogMessage END
(@filename NOT LIKE @backupPathRoot + '%') BEGIN
@backuptype = ''
SET @LogMessage = 'Differential Backup (' + @filename + ') in wrong folder'
EXEC BackupLogEntry 'BackupDatabase', @database, @LogMessage
..xp_fileexist @filename, @FileExists output
IF (@FileExists = 0) BEGIN
@backuptype = ''
SET @LogMessage = 'Differential Backup (' + @filename + ') missing'
EXEC BackupLogEntry 'BackupDatabase', @database, @LogMessage
@backuptype = '' BEGIN
@fullbackupsetid IS NULL SET @backuptype='Full'
ELSE SET @backuptype='Differential'
next FROM cur INTO @filename

@now = GETDATE()SET @folder = @backupPathRoot + @@SERVERNAME + '\' + @database + '\'SET @folder = REPLACE(@folder,'/','')EXEC MASTER..xp_create_subdir @folderSET @filename = @folder + @database + ' ' + REPLACE(CONVERT(VARCHAR,@now,121),':','-') + ' ' + @backuptype + '.bak'SET @filename = REPLACE(@filename,'/','')
SET @LogMessage = 'Taking a ' + @BackupType + ' Backup,  Filename: ' + @filenameEXEC BackupLogEntry 'BackupDatabase', @database, @LogMessage
IF @backuptype = 'Log'
BACKUP LOG @database TO DISK=@filename ELSE IF @backuptype = 'Differential'
BACKUP DATABASE @database TO DISK=@filename WITH  differentialELSE IF @backuptype = 'Full'
BACKUP DATABASE @database TO DISK=@filename GO--alter database robtest set recovery full

--BackupDatabase 'RobTest','Differential','C:\Backup'

Wednesday, February 15, 2012

Backup Script - Needs to be updated

Ack, It's been a while since I've updated my SQL backup script.  I've completely reworked it.  I'm in class this week, but will post it early next week.

Friday, January 27, 2012

Integrate Resharper Unit Test Runner after a successful build

Whilst trying to use TDD properly, I noticed that I'd have to build my solution and then run my unit tests. Now, most of the unit test runners will build your project at the same time, but it still was a little less convenient than I'd like.

I scraped together a few snippets from teh interwebs and came up with the following to be added to the VS Macros: Pop open the Macros IDE (Tools - Macros - Macros IDE) and in the Project Explorer choose the Environment Events Module. Then Add the following code:

Private SolutionBuildSuccess As Boolean

Private Sub BuildEvents_OnBuildBegin( _
   ByVal Scope As vsBuildScope, _
   ByVal Action As vsBuildAction _
) Handles BuildEvents.OnBuildBegin
  SolutionBuildSuccess = True
End Sub

Private Sub BuildEvents_OnBuildProjConfigDone( _
  ByVal Project As String, _
  ByVal ProjectConfig As String, _
  ByVal Platform As String, _
  ByVal SolutionConfig As String, _
  ByVal Success As Boolean _
) Handles BuildEvents.OnBuildProjConfigDone
  If Success = False Then
    SolutionBuildSuccess = False
  End If
End Sub

Private Sub BuildEvents_OnBuildDone( _
  ByVal Scope As EnvDTE.vsBuildScope, _
  ByVal Action As EnvDTE.vsBuildAction _
) Handles BuildEvents.OnBuildDone
  If SolutionBuildSuccess Then
    Dim win As Window
    win = DTE.ActiveWindow
  End If
End Sub

Going through each of these:
SolutionBuildSuccess: This is just a boolean that will be true if every project builds.  No sense in even running tests if you can't even compile

BuildEvent_OnBuildBegin: This just resets the SolutionBuildSuccess at the begin of each build

BuildEvents_OnProjConfigDone: So, there are 2 events when a build finishes.  One fires when the complete build process is done (OnBuildDone).  This one, however, does not carry the result of the project build(s).  So, there is OnProjConfigDone which fires for each project that builds and has the result.  This will just set the SolutionBuildSuccess to false if any of the projects fail to build.

BuildEvents_OnBuildDone: This one fires when the build is all done.  So, I need to check to see if all the projects built successfully.  Then I save the currently active window.  Finally we run the tests and make that previously saved active window active again.  The reason for that is that after I'm done building and running the tests, generally I'm ready to code more.  So, instead of remembering that I have to smash ctrl-tab (I think) to jump back, I took the lazy approach and put it into the step.

Now, I remember back in the old days when F6 was the command to build stuff.  I'm not sure what one of my tomfooleries changed it, but it did.  So, I re-assigned it back to being my keyboard shortcut for build.

Any Comments or suggestions will probably be ignored, but feel free to leave them anyway.. 

Friday, September 2, 2011

Notepad++ Customize context Menu with TextFX

First off, I have used a bunch of different editors in my day, and have finally settled on Notepad++ ... the main reasons are:

  • It's Free
  • It has a fairly small footprint when it runs
  • It can be portable
  • It's customizable up the yin yang

This last point brings me to why I'm posting. You can add new items to the context menu which is a huge plus. The one I needed, however, was sort. I commonly will have a few lines of text that I'll need to sort

I'll have this:

I'll want this:

There is a really cool plugin called TextFX that has a command "Sort lines case insensitive (at column)". So, I figured I'd chuck that into the context menu.

Turns out, that integrating TextFX is not trivial. In fact, it's cryptic beyond belief. I think I finally figured out how to do it. I found a reference for the Notepad++ command names and used this in conjunction with another post regarding integrating TextFX into the context menu to figure out how to do all of this.

First off, TextFX is not integrated in the proper way with Notepad++. So, I had to do some playing.

In the aforementioned blog post the OP was trying to integrate the Rewrap Text command into the context menu. They were able to achieve this by using the PluginEntryName of "TextFX Characters". That's all well and good, but the Rewrap command is actually under the TextFX Edit menu. What I think is happening is that Notepad++ is seeing the TextFX Characters menu first and using that as the PluginEntryName. So, back to my issue, I found the corresponding command name for sorting on the command name reference page (T:Sort lines case insensitive (at column)) popped it in... reload N++ ... and ... poof... sort in the context menu.

For those of you that just want the code, here it is: <Item PluginEntryName="TextFX Characters" PluginCommandItemName="T:Sort lines case insensitive (at column)" ItemNameAs="Sort Lines" />

Happy Notepad++ing

Thursday, September 23, 2010

Invalid URI scheme 'file://' for map control. Control must be hosted in a HTTP(s) website.

But... I'm not using file:// for my map control.

So, I'm programming with ESRI's Silverlight Map control. This started out as a test project, but it's become more of a production application. Instead of moving all my code to a new project, I'll just rename it from SilverlightApplication1 to MyProdcutionApplication.

I rename all the namespaces, remembering to rename the corresponding XAML references that don't get refactored. Then I rename the projects themselves and have to organize the folders.

So, I remove all the projects from the solution, rename the folders, and add them back in. I hit compile and woohoo, no errors. So, I go to run it and I get the following gem:

I recheck my map references and sure enough, I <am> using http://path/to/my/rest/endpoint ... so.. off to google.

After a 20 minute search trying all variations of the error, I finally stumble upon this thread in the ESRI forums. Back in 2009, Don Freeman got the same error and lo and behold, it was because the default start project was the Silverlight Application, not the Test Website.

I flipped my startup project to the test web app and poof... all is well

Thursday, July 8, 2010

Logon Failed in SSMS on Windows 2008 (or Windows 7)

We recently moved our SQL Server from Windows Server 2003 to Windows Server 2008. When I tried to connect through SSMS on the server I got a rather odd error:

Now, I'm a SQL Administrator and am able to connect to the server from my local PC (Windows XP). I check the SQL Event log and see the following:
. After some Googling I find an article referencing UAC. It suggests right clicking on SSMS and running as administrator. I do this, accept the annoying message, and yowza, the system works.

Now, this is all well and good, but I really don't want to have to right click and run as administrator each time. Plus, as infrequently as I actually log on to the server to use SSMS, I'm sure I'll forget and end up googling this exact same error again. So, I poked around a little bit and found out that you can force the .exe to always run as administrator

  • SSMS Properties
  • Compatability Tab
  • Show Settings for all users

    Notice how this is now for the ssms.exe file, not for the shortcut anymore
  • Check "Run this program as administrator"

Now, whenever you run SSMS on the server, you will get the annoying message and it will work. I'm a little surprised that Microsoft didn't include that in the install package for SQL Server 2008, but at least there's a pretty easy fix.