# Wednesday, July 25, 2007

SQL Server - Save all DTS Packages

Here's another useful proc I found to back up all your DTS packages (from I can't remember where).  Put this in your master database: 

Create procedure usp_SavePackages
@Path    
varchar(128
)
as
/*

*/

    
set nocount on

declare 
@objPackage 
int
declare 
@PackageName varchar(128
)
declare @rc 
int
declare 
@ServerName varchar(128
)
declare @FileName varchar(128
)
declare    @FilePath varchar(128
)
declare    @cmd varchar(2000
)
    
    
select     @ServerName 
@@ServerName ,
        @FilePath 
@Path
    
    
if right(@Path,1) <> 
'\'
    begin
        select @Path = @Path + '
\
'
    end
    
    -- create output directory - will fail if already exists but ...
    select    @cmd = '
mkdir 
' + @FilePath
    exec master..xp_cmdshell @cmd
    
    
create table #packages (PackageName varchar(128))
    insert     #packages
        (PackageName)
    select     distinct name
    from    msdb..sysdtspackages
    
    select    @PackageName = ''
    while @PackageName < (select max(PackageName) from #packages)
    begin
        select    @PackageName = min(PackageName) from #packages where PackageName > @PackageName

        select    @FileName = @FilePath + @PackageName + '
.dts
'

        exec @rc = sp_OACreate '
DTS.Package
', @objPackage output
        if @rc <> 0
        begin
            raiserror('
failed to create package rc %d
', 16, -1, @rc)
            return
        end

        exec @rc = sp_OAMethod @objPackage, '
LoadFromSQLServer
' , null,
            @ServerName = @ServerName, @Flags = 256, @PackageName = @PackageName
        if @rc <> 0
        begin
            raiserror('
failed to load package rc %d, package %s
', 16, -1, @rc, @PackageName)
            return
        end
        
        -- delete old file
        select @cmd = '
del 
' + @FileName
        exec master..xp_cmdshell @cmd, no_output
        
        exec @rc = sp_OAMethod @objPackage, '
SaveToStorageFile
', null, @FileName
        if @rc <> 0
        begin
            raiserror('
failed to save package rc %d, package %s
', 16, -1, @rc, @PackageName)
            return
        end
        
        exec @rc = sp_OADestroy @objPackage
    end

GO

Colorized by: CarlosAg.CodeColorizer

posted on Wednesday, July 25, 2007 9:05:39 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Monday, July 23, 2007

Microsoft CRM - Data Migration Framework (DMF) Bugs / Fixes

I've been migrating data for a client recently and part of this has required the use of the MS Data Migration Framework (DMF).  I thought I'd post a couple of bugs I found and(thankfully) how I managed to get past them with the use of SQL Profiler!

The Migration Framework works - albeit slowly as it uses the CRM Web Service to stuff the records into the CRM database.  The CRM structure I've been trying to migrate to contains a large number of custom fields.  The DMF calls many stored procs to get its data (from the CDF database); these are structured by entity e.g

p_dm_GetContact
p_dm_GetContract

I was having a problem with Contacts, and after tracing a migration attempt with cut-down data (for speed), I found that the issue was that the lid was being blown off a VARCHAR(8000) in p_dm_GetContact giving the following SQL Error:

Error: 207, Severity: 16, State: 3

The error is 'Invalid Column Name'.  Further investigation revealed that a slightly dodgy piece of code (replicated in all the other similar procs) effectively truncates the dynamically created field list in the 'SELECT' statement...

-- if there are extended fields, then attached them to the end 
--   of the base field list

if len(@metadatafields) > 0
    set @entityfields = @entityfields + ', ' + rtrim(@metadatafields)

@entityfields contains a comma delimited list of fields from the 'cdf_Contact' table, and @metadatafields contains fields from 'cdf_Contact_Ext' (custom fields).  If you concatenate the two you're obviously asking for trouble.

The following shows the 'fix' to enable a bit more room (it's still not very good - but as the code's not available we can't make a nicer fix right now)

CREATE procedure p_dm_GetContact
as
begin
    set nocount on
    
    declare 
@entity            varchar(100
)
    
declare @entityfields     varchar(8000
)
    
declare @metadatafields varchar(8000
)
    
-- set target entity name
    
set @entity 
'Contact'

    
-- query base field list for targeted entity
    
exec p_dm_GetFields @entity, 'c', @entityfields 
output

    
-- query extended field list for targeted entity
    
exec p_dm_GetMetaDataFields @entity, 'ext', @metadatafields 
output

    
-- if there are fields, then attached them to the end 
        --   of the base field list    
    
if len(@entityfields) > 
0
       
set @entityfields ', ' rtrim
(@entityfields)

    
-- if there are extended fields, then attached them to the end 
        --   of the base field list
------    if len(@metadatafields) > 0
------        set @entityfields = @entityfields + ', ' + rtrim(@metadatafields)

    
if len(@metadatafields) > 0
        
set @metadatafields ', ' rtrim
(@metadatafields)

    
-- build/execute query statement
    -- the status code must be null
    
exec (
'    select top 1 '''' As CRMUpdateStatus, 
            c.ContactId As entityid,
            c.StateCode As statecode,
            dbo.p_fn_GetID(l.DestinationId) As originatingleadid, 
            dbo.p_fn_GetID(p.DestinationId) As defaultpriceLevelid, 
            dbo.p_fn_GetID(a.DestinationId) As accountid, 
            dbo.p_fn_GetID(su.DestinationId) As owninguser 
            ' 
+ @entityfields + @metadatafields + 
'

                 from cdf_Contact c

       inner join cdf_Contact_info info 
                   on c.ContactId = info.ContactId

       left outer join cdf_Contact_ext ext 
                   on c.ContactId = ext.ContactId

      left outer join cdf_Lead_info l 
                   on c.OriginatingLeadId = l.LeadId

      left outer join cdf_PriceLevel_info p 
                   on c.DefaultPriceLevelId = p.PriceLevelId

      left outer join cdf_Account_info a 
                   on c.AccountId = a.AccountId

           inner join cdf_SystemUser_info su 
                   on c.OwningUser = su.SystemUserId

           where (info.StatusCode is null)
         and (info.DestinationId is null)'
)
end
GO


Colorized by: CarlosAg.CodeColorizer

 

posted on Monday, July 23, 2007 2:48:07 PM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Thursday, July 19, 2007

SQL Server - Get Matrix for two different result sets

I had a need today to splice 2 different result sets together and effectively give all permutations of the two tables.  I'd seen different examples on doing this with cursors and loops etc, but I thought - there 'must' be an easier way - there's lots of different join types right?

The first issue I'd got is that there's no common fields between the two tables I need to merge because otherwise a 'simple' FULL OUTER JOIN would do the biz.

In the end it didn't amount to much more than that because a slightly dodgy hack (but functional nontheless), to guarantee full coverage from the join is to simply place the same literal on both sides of the join :-)

With 2 sets of 'different' data (let's use Northwind as I'm not about to bore you with my own data).  Say you wanted to generate a list of all products for all regions given the following result sets...

SELECT ProductName
FROM Products

SELECT RegionDescription
FROM Region

You can splice them together with this....

SELECT  R.RegionDescription,
        P.ProductName
FROM Products P
FULL OUTER JOIN Region R
    ON 1 = 1

and you get something like this... (snippet)

RegionDescription                                  ProductName                             
-------------------------------------------------- ----------------------------------------
Eastern                                            Valkoinen suklaa
Eastern                                            Vegie-spread
Eastern                                            Wimmers gute Semmelknödel
Eastern                                            Zaanse koeken
Western                                            Alice Mutton
Western                                            Aniseed Syrup
Western                                            Boston Crab Meat

If there's actually a common field then your join can obviously use that.  Any other filtering you want to do can also go into the WHERE clause as required.

This is another reminder of the lesson that if you're tempted to use a CURSOR, then resist that urge until you've exhausted your Google finger!

SQLServerCentral and SQLMag often have the answer you're looking for.

posted on Thursday, July 19, 2007 10:48:06 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Friday, July 06, 2007

Cryptography - Using Base64 to encrypt/decrypt Strings rather than Unicode or ASCII - Error - 'Length of the data to decrypt is invalid'

I recently did a conversion from .NET 1.1 to .NET 2.0 for a particular project.  The framework classes were still in 1.1 (where our TripleDES encryption library lives).  Unit Tests still run in 1.1 and all pass.

A production problem then started to show 'Length of the data to decrypt is invalid' and I was horribly confused as I'd inherited some of the code and all seemed good...

I thankfully found the following ... http://blogs.msdn.com/shawnfa/archive/2005/11/10/491431.aspx that explained why it's really not a good idea to assume that even though you're only encrypting ASCII characters, you don't use 7 or 8 bit encoding to encrypt/decrypt.  The key is in the fact that the overall sequence of bytes isn't guaranteed to be valid Unicode or ASCII.

(Why do the unit tests pass?) - because the Cryptography classes in .NET framework were revamped for V2.0 and validation tightened up.  As Shawn says - it's better that it doesn't successfully decrypt into an invalid string.

This leaves a bit of a tidyup of course as I've now got to re-stuff all encrypted data into the database and patch the apps to ensure that the correct encryption is used.  I've also got to find a way to support existing files and string encrypted with the class as some code is still happily using this in 1.1-land and some is clearly 'not' working in 2.0-land - fun!

I guess it's a lesson that today's code won't necessarily work tomorrow - and you shouldn't discount breakages from framework changes when you're investigating issues.

posted on Friday, July 06, 2007 9:12:30 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Friday, June 29, 2007

Grammar refresher - Using punctuation properly

I've found that I often use colons in technical documentation and emails, yet I've probably never 'really' understood quite how they or semicolons should be used.  I think my school days were after the golden age of English Grammar education (or maybe I just didn't pay attention).

After reading this, I'm now clear on the picture.  I could rephrase that as follows (just to force a semicolon in):

I read this article; I'm now clear on the picture, and shall use semicolons to appear clever.

posted on Friday, June 29, 2007 11:16:27 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Thursday, June 28, 2007

Learning through Anti-Patterns

I was gaining much (renewed) amusement through anti-patterns the other day when I took a look at Scott's blog on 'real world' development approaches, and subsequently dug a bit further on management and development anti-patterns on Wikipedia.

You can also find a bit more info here.

It then struck me that although these are absolute classics that we've all seen (management ones 'all the time'), I probably don't know any developer (including myself) that could truly say they've never been guilty of at least one of these.  I'd go further and say that some of these anti-patterns can form an effective apprenticeship for software development, as you really need to know what not to do as much as what to do in regards to design and coding approaches.

  • Who hasn't hard-coded?
  • Who hasn't read an article on some cool way of doing things, then taken it all out of context and ended up doing something like error hiding, or mistaken delegation for poltergeists?
  • Who hasn't thought (eugh - a world of pain will ensue if I try and get rid of this code) - the lava flow.

I could go on all day here as 'so many' of the Wikipedia anti-patterns ring true as something I've encountered (and still encounter) along the way.

Ultimately none have killed me so I guess therefore have made me stronger and hopefully a little wiser on what not to do.

posted on Thursday, June 28, 2007 12:03:16 PM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Tuesday, June 26, 2007

SQL Server - Kill all users script

Found this the other day - for when you're restoring SQL databases.  You need to kick out all current users right?

Run this little fella into your master database and away you go - just run this step before your restore.

CREATE PROCEDURE usp_KillUsers @dbname varchar(50as
SET NOCOUNT ON

DECLARE 
@strSQL varchar(255)
PRINT 'Killing Users'
PRINT '-----------------'

CREATE table #tmpUsers(
spid 
int,
dbname 
varchar(50))

INSERT INTO #tmpUsers
SELECT 
[Process ID]    l.req_spid, db_name(l.rsc_dbid)
from master.dbo.syslockinfo l with (NOLOCK
where db_name(l.rsc_dbid) @dbname
group by l.req_spid, db_name(l.rsc_dbid)


DECLARE LoginCursor CURSOR
READ_ONLY
FOR SELECT spid, dbname FROM #tmpUsers WHERE dbname @dbname

DECLARE @spid varchar(10)
DECLARE @dbname2 varchar(40)
OPEN LoginCursor

FETCH NEXT FROM LoginCursor INTO @spid, @dbname2
WHILE (@@fetch_status <> -1)
BEGIN
    IF 
(@@fetch_status <> -2)
    
BEGIN
    PRINT 
'Killing ' + @spid
    
SET @strSQL 'KILL ' + @spid
    
EXEC (@strSQL)
    
END
    FETCH 
NEXT FROM LoginCursor INTO  @spid, @dbname2
END

CLOSE 
LoginCursor
DEALLOCATE LoginCursor

DROP table #tmpUsers

Colorized by: CarlosAg.CodeColorizer
posted on Tuesday, June 26, 2007 8:15:39 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Wednesday, June 13, 2007

Signs of Discontent? - Increased Food and Coffee

I like observing human behaviour at work and I've noticed recently that the amount of 'junk' food appearing in the dev pod has increased quite a bit.  People are also going for more coffee breaks (as far as I can see).  I may be imagining this, but I think it's probably a good indicator of any or all of the following:

  • Boredom
  • Lack of enthusiasm for the current work
  • Need for stress relief (mucho long hours)

I'd have to admit the current project has its frustrations (integration work with a 'less than extendable' 3rd party product) and I'd probably rather be doing something else - so I'll keep my eye on this and see what happens when the supplies run out!

I think there's a bit of synergy here with the triangle of happiness.  My own experience is if you're happy and engaged in what you're doing then you often don't even find the time to eat..

posted on Wednesday, June 13, 2007 8:22:56 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]