<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2681714432624335789</id><updated>2012-02-16T05:00:00.055-08:00</updated><title type='text'>DynamicsGeek</title><subtitle type='html'>Exploring the technology behind Microsoft Dynamics GP...Some of it at least.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>33</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-9124546957583302726</id><published>2010-08-16T18:40:00.000-07:00</published><updated>2010-08-16T18:40:08.135-07:00</updated><title type='text'>My Shiny New Blog</title><content type='html'>Well, I think it's time to admit that work in Dynamics GP is taking an extended break.&amp;nbsp; With a new position, comes new focus, and sadly GP is not a part of that.&amp;nbsp; However, I have certainly not left the world of technology, but have moved out of the world of debits and credits to pure database development.&lt;br /&gt;&lt;br /&gt;In that vein, I have launched a new blog, &lt;a href="http://datadevgeek.blogspot.com/"&gt;DataGeek&lt;/a&gt;, that will be more data and database development focused.&amp;nbsp; Life is journey into the unknown and I may yet return to GP, it's certainly been fun.&lt;br /&gt;&lt;br /&gt;Until then....&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-9124546957583302726?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/9124546957583302726/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/08/my-shiny-new-blog.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/9124546957583302726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/9124546957583302726'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/08/my-shiny-new-blog.html' title='My Shiny New Blog'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-4414244820118809070</id><published>2010-04-09T19:45:00.000-07:00</published><updated>2010-04-09T19:45:00.786-07:00</updated><title type='text'>Strip Time from SQL Date</title><content type='html'>&lt;p&gt;Often, particularly when saving dates to Dynamics GP, we need to strip the time out of a SQL date value.  I found a neat little trick for doing that recently in an &lt;a href="http://www.sqlservercentral.com/articles/Date+Manipulation/69694/" target="_blank"&gt;article&lt;/a&gt; on SQLServerCentral.com by Seth Phelabaum.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;SELECT DATEADD(dd, DATEDIFF(dd,0,GETDATE()), 0).&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This works by getting the number of days since date 0, which truncates the time, and then adding it back on to date 0.  &lt;/p&gt;&lt;p&gt;In his &lt;a href="http://www.sqlservercentral.com/articles/Date+Manipulation/69694/" target="_blank"&gt;article&lt;/a&gt; he has a few other DATEADD/DATEDIFF tricks.  It’s well worth a read.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-4414244820118809070?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/4414244820118809070/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/04/strip-time-from-sql-date.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4414244820118809070'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4414244820118809070'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/04/strip-time-from-sql-date.html' title='Strip Time from SQL Date'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-332227841033403383</id><published>2010-04-07T19:04:00.001-07:00</published><updated>2010-04-07T19:04:46.938-07:00</updated><title type='text'>King Me</title><content type='html'>&lt;p&gt;It appears that I had a bit of a hiatus from blogging.&amp;#160; This was primarily due to a whirlwind of a successful job search, followed by a brief transition.&amp;#160;&amp;#160; The search itself was quite enlightening as the last time I formally interviewed for a job was 1997 and the last time I actually got a job from the resume/interview process was 1987.&lt;/p&gt;  &lt;p&gt;Anyway, during one of my interviews I was asked what I would do as a manager if three of my developers came up with equally good but different solutions to a problem but couldn’t agree on which one to implement.&amp;#160; My answer was something to the affect that, although I prefer to be a consensus builder, management by committee is no management at all, the buck would stop with me and I would have to pick one.&lt;/p&gt;  &lt;p&gt;My interviewer had another interesting idea.&amp;#160; If remember it correctly, he called it his “King Theory”.&amp;#160; What he would do is pick one of the developers and make him or her “The King”.&amp;#160; It would be his or her job to pick one of the solutions and be responsible for it’s success.&amp;#160; They would be able to pick any or any combination of the solutions but the buck would stop with them.&lt;/p&gt;  &lt;p&gt;Now the interesting question is what would be the best solution for the developer to choose and what would they?&amp;#160; The natural instinct would probably be to pick their own but might it not be better to pick someone else’s or a combination to invest the other team members in the success of the implementation?&lt;/p&gt;  &lt;p&gt;I thought it was a pretty interesting solution to the problem.&amp;#160; Has anyone else run across this?&amp;#160; What do you think of it?&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-332227841033403383?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/332227841033403383/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/04/king-me.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/332227841033403383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/332227841033403383'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/04/king-me.html' title='King Me'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-8053363922201452723</id><published>2010-02-19T12:19:00.000-08:00</published><updated>2010-02-19T19:04:42.789-08:00</updated><title type='text'>SQL Server Recursive Common Table Expressions…And Other Ways to Generate a Stack Overflow in SQL Server</title><content type='html'>&lt;h5&gt;&lt;/h5&gt;  &lt;p&gt;OK, so won’t actually generate a stack overflow error in SQL Server, but it makes for a good title. Some time ago I was asked to write a custom deferred revenue integration into GP. There were some quirks to their recognition schedule that prevented them from using the standard deferred revenue module.&lt;/p&gt;  &lt;p&gt;For those of you not familiar with deferred revenue, here’s a quick primer. If you pay $24 for a 12 month magazine subscription, the publisher is not allowed to recognize and report to their stockholders the entire $24 when they receive it. They can only recognize revenue as they’ve delivered the service, in this case each magazine, to you. As such, they will recognize and report $2 in revenue each month.&lt;/p&gt;  &lt;p&gt;OK, so how do you write a SQL statement that takes a set of rows and creates multiple rows from each individual row.&amp;#160; Let’s say that we have a sales table, tblSales, that has an amount to be recognized, a starting month and a number of months to be recognized over as follows:&lt;/p&gt;  &lt;table cellspacing="0" cellpadding="2" width="411" border="1"&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign="top" width="108"&gt;OrderNumber&lt;/td&gt;        &lt;td valign="top" width="65"&gt;Amount&lt;/td&gt;        &lt;td valign="top" width="100"&gt;StartingMonth&lt;/td&gt;        &lt;td valign="top" width="136"&gt;RecognitionMonths&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="108"&gt;1&lt;/td&gt;        &lt;td valign="top" width="65"&gt;         &lt;p align="right"&gt;$120.00&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="100"&gt;         &lt;p align="right"&gt;3/2010&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="136"&gt;         &lt;p align="right"&gt;3&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="108"&gt;2&lt;/td&gt;        &lt;td valign="top" width="65"&gt;         &lt;p align="right"&gt;$257.00&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="100"&gt;         &lt;p align="right"&gt;2/2010&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="136"&gt;         &lt;p align="right"&gt;7&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;To make this easy, we’ll recognize the revenue equally over the number of months. Often revenue needs to be recognized according to the number of days in the month and take into account starting and ending dates that are mid-month. In this case we’ll use a simple model and simply need to return a dataset of 10 rows as follows:&lt;/p&gt;  &lt;table cellspacing="0" cellpadding="2" width="400" border="1"&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign="top" width="133"&gt;OrderNumber&lt;/td&gt;        &lt;td valign="top" width="133"&gt;AmountRecognized&lt;/td&gt;        &lt;td valign="top" width="133"&gt;MonthRecognized&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;1&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$40.00&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;3/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;1&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$40.00&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;4/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;1&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$40.00&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;5/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;2&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$36.71&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;2/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;2&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$36.71&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;3/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;2&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$36.71&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;4/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;2&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$36.71&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;5/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;2&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$36.71&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;6/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;2&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$36.71&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;7/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="133"&gt;2&lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;$36.74&lt;/p&gt;       &lt;/td&gt;        &lt;td valign="top" width="133"&gt;         &lt;p align="right"&gt;8/2010&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;So how, in a SQL statement, can we get from 2 rows to 10? As with any problem, I’m sure we could come up with any number of solutions using WHILE loops and table variables, but I’m going to use the opportunity to introduce recursion in SQL.&lt;/p&gt;  &lt;p&gt;Recursion is a feature of the T-SQL &lt;a href="http://dynamicsgpgeek.blogspot.com/2010/01/common-table-expressions-dispensing.html" target="_blank"&gt;Common Table Expression&lt;/a&gt;. It is essentially a common table expression (CTE) that references itself. Let’s look at the following statement:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;WITH DeferredRevenueEntries(     &lt;br /&gt;OrderNumber      &lt;br /&gt;,AmountRecognized      &lt;br /&gt;,MonthRecognized      &lt;br /&gt;,RecognitionMonths      &lt;br /&gt;,CurrentMonthIndex) AS      &lt;br /&gt;(      &lt;br /&gt;-- Anchor member      &lt;br /&gt;-- The anchor member creates the entry for the first month.      &lt;br /&gt;SELECT      &lt;br /&gt;OrderNumber      &lt;br /&gt;,AmountRecognized = Amount / RecognitionMonths      &lt;br /&gt;,MonthRecognized = StartingMonth      &lt;br /&gt;,RecognitionMonths      &lt;br /&gt;,1 AS CurrentMonthIndex      &lt;br /&gt;FROM tblSales      &lt;br /&gt;UNION ALL      &lt;br /&gt;-- Recursive member      &lt;br /&gt;-- Recurse one time for each additional month to be recognized      &lt;br /&gt;SELECT      &lt;br /&gt;OrderNumber      &lt;br /&gt;,AmountRecognized      &lt;br /&gt;,MonthRecognized = DATEADD(month, 1, MonthRecognized)      &lt;br /&gt;,RecognitionMonths      &lt;br /&gt;,CurrentMonthIndex = CurrentMonthIndex + 1      &lt;br /&gt;FROM DeferredRevenueEntries      &lt;br /&gt;WHERE CurrentMonthIndex &amp;lt; RecognitionMonths      &lt;br /&gt;) &lt;/p&gt;    &lt;p&gt;SELECT     &lt;br /&gt;OrderNumber      &lt;br /&gt;,AmountRecognized      &lt;br /&gt;,MonthRecognized      &lt;br /&gt;FROM DeferredRevenueEntries      &lt;br /&gt;ORDER BY      &lt;br /&gt;OrderNumber      &lt;br /&gt;,MonthRecognized&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here we’re defining a CTE, DeferredRevenueEntries. This is a UNION query. the first part of the union is called the anchor member. This is a SELECT statement against our source table, tblSales. This part of will return the first month rows for each of the sales records, with the amount to be recognized being the total amount divided by the number of months.&lt;/p&gt;  &lt;p&gt;The second part is the recursive member. Note that the FROM clause is referencing the CTE itself. This will take the initial two records from the first part of the union, and keep returning them, adding one month each time. To make sure that we only return the number of records matching the number of months to be recognized, we add the field CurrentMonthIndex, increment it each time we recurse and make sure we only return records where the CurrentMonthIndex is less than the number of months to be recognized.&lt;/p&gt;  &lt;p&gt;OK, so what happens if we forget to put in the WHERE clause in the recursive member? Will we in fact cause the aforementioned stack overflow? The answer is no. SQL is too smart for that. Instead you will recieve the following error:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Msg 530, Level 16, State 1, Line 1     &lt;br /&gt;The statement terminated. The maximum recursion 100 has been exhausted before statement completion.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;By default, SQL Server limits you to a maximum recursion level of 100. It is possible to override that limit by using the MAXRECURSION option in the final SELECT statement such that it looks as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT     &lt;br /&gt;OrderNumber      &lt;br /&gt;,AmountRecognized      &lt;br /&gt;,MonthRecognized      &lt;br /&gt;FROM DeferredRevenueEntries      &lt;br /&gt;ORDER BY      &lt;br /&gt;OrderNumber      &lt;br /&gt;,MonthRecognized      &lt;br /&gt;OPTION (MAXRECURSION 1000)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Now you might say, well what if I put in a MAXRECURSION of 10000000000? Is there some way I can create a stack overflow? Again, SQL will outwit you. The maximum MAXRECURSION limit is 32767.&lt;/p&gt;  &lt;p&gt;OK, so there’s one final issue. You’ll note in my results example above that the recognition amount for order 2 was $36.71 except for the last entry where it’s $36.74. This is because never met an accountant who could deal with as much as a single penny off no matter how high the total. To fix that I create the following modified version of the statement:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;WITH DeferredRevenueEntries(     &lt;br /&gt;OrderNumber      &lt;br /&gt;,AmountRecognized      &lt;br /&gt;,MonthRecognized      &lt;br /&gt;,RecognitionMonths      &lt;br /&gt;,CurrentMonthIndex) AS      &lt;br /&gt;(      &lt;br /&gt;-- Anchor member      &lt;br /&gt;-- The anchor member creates the entry for the first month.      &lt;br /&gt;SELECT      &lt;br /&gt;OrderNumber      &lt;br /&gt;,AmountRecognized = ROUND((Amount / RecognitionMonths), 2)      &lt;br /&gt;,MonthRecognized = StartingMonth      &lt;br /&gt;,RecognitionMonths      &lt;br /&gt;,1 AS CurrentMonthIndex      &lt;br /&gt;FROM tblSales      &lt;br /&gt;UNION ALL      &lt;br /&gt;-- Recursive member      &lt;br /&gt;-- Recurse one time for each additional month to be recognized      &lt;br /&gt;SELECT      &lt;br /&gt;OrderNumber      &lt;br /&gt;,AmountRecognized      &lt;br /&gt;,MonthRecognized = DATEADD(month, 1, MonthRecognized)      &lt;br /&gt;,RecognitionMonths      &lt;br /&gt;,CurrentMonthIndex = CurrentMonthIndex + 1      &lt;br /&gt;FROM DeferredRevenueEntries      &lt;br /&gt;WHERE CurrentMonthIndex &amp;lt; (RecognitionMonths - 1)      &lt;br /&gt;) &lt;/p&gt;    &lt;p&gt;SELECT     &lt;br /&gt;OrderNumber      &lt;br /&gt;,AmountRecognized      &lt;br /&gt;,MonthRecognized      &lt;br /&gt;FROM DeferredRevenueEntries &lt;/p&gt;    &lt;p&gt;UNION ALL &lt;/p&gt;    &lt;p&gt;-- Create last entry manually to adjust for rounding errors. &lt;/p&gt;    &lt;p&gt;SELECT     &lt;br /&gt;tblSales.OrderNumber      &lt;br /&gt;,AmountRecognized = ROUND(tblSales.Amount - DeferredRevenueEntryTotals.TotalAmountRecognized, 2)      &lt;br /&gt;,MonthRecognized = DATEADD(month, 1, DeferredRevenueEntryTotals.LastMonthRecognized)      &lt;br /&gt;FROM tblSales      &lt;br /&gt;INNER JOIN (      &lt;br /&gt;SELECT      &lt;br /&gt;OrderNumber      &lt;br /&gt;,TotalAmountRecognized = SUM(AmountRecognized)      &lt;br /&gt;,LastMonthRecognized = MAX(MonthRecognized)      &lt;br /&gt;FROM DeferredRevenueEntries      &lt;br /&gt;GROUP BY OrderNumber) AS DeferredRevenueEntryTotals      &lt;br /&gt;ON tblSales.OrderNumber = DeferredRevenueEntryTotals.OrderNumber &lt;/p&gt;    &lt;p&gt;ORDER BY     &lt;br /&gt;OrderNumber      &lt;br /&gt;,MonthRecognized&lt;/p&gt;    &lt;p&gt;You’ll notice a few changes here. The first is that I round the AmountRecognized value to 2 digits. The second is that the CTE is now only going to recurse through the number of months – 1. This is because we’re create the final entry in the last select in the UNION query by taking the total sales amount from tblSales and then subtracting the total recognized prior to the final month, giving us an amount that is adjusted for any rounding issues. &lt;/p&gt;    &lt;p&gt;I hope that this has given you a brief look at the power of the recursive CTE and that you are reassured that you will not break SQL Server by using it. There are certainly many other ways to use the, such as traversing hierarchical lists. Go ahead, recurse away!&lt;/p&gt;&lt;/blockquote&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-8053363922201452723?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/8053363922201452723/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/02/sql-server-recursive-common-table.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/8053363922201452723'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/8053363922201452723'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/02/sql-server-recursive-common-table.html' title='SQL Server Recursive Common Table Expressions…And Other Ways to Generate a Stack Overflow in SQL Server'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-4715982584925524453</id><published>2010-02-03T18:47:00.001-08:00</published><updated>2010-02-19T16:29:37.345-08:00</updated><title type='text'>Wither the SQL Developer? Is T-SQL the Next Assembly Language?</title><content type='html'>&lt;p&gt;So today I’m going to veer completely away from GP and talk about SQL in general. The reality is that I’m a data and database geek first and a Dynamics GP geek second; and my (current) database language of choice is T-SQL.&lt;/p&gt;  &lt;p&gt;But what is the future of that technology?&amp;#160; Is T-SQL a data dinosaur?&amp;#160; Years ago I actually programmed in assembly language, the language of the CPU itself (ok so technically that would be machine code but let’s not split hairs).&amp;#160; Today, processing power and memory have gotten to the point that you can run a language like PHP or C# so many layers above the CPU as to make it invisible.&amp;#160; You have an JIT compiled language running on a CLR on top of an operating system on a virtual processor on a hypervisor on a physical processor.&amp;#160; Only the most determined driver developer would even consider assembly.&lt;/p&gt;  &lt;p&gt;So what of T-SQL?&amp;#160; With Visual Studio 2010, Microsoft will be releasing their latest iteration of the ADO.NET Entity Framework, the latest in a long line of tools to abstract away the data layer and hide the dirty underwear of SQL from the developer.&amp;#160; The Entity Framework essentially allows you to design, build and implement your database without ever having to get your hands dirty in SQL Management Studio.&lt;/p&gt;  &lt;p&gt;Tools like the Entity Framework, LINQ and even the old standby, ADO.NET datasets allow you to do some pretty cool things like being able to join data and enforce referential integrity across disparate data sources and in-memory data structures.&amp;#160; They make it easier to make your applications database agnostic.&lt;/p&gt;  &lt;p&gt;To be fair, there has always been significant proportion of the developer community who have lived by the philosophy, for some very valid reasons, that the database should be simply a dumb data store.&amp;#160; Even GP was built with that philosophy when it supported multiple back end technologies.&lt;/p&gt;  &lt;p&gt;I have tended towards embedding as much logic as possible in the database itself; to use the power of functions, views, referential integrity and constraints; to make make stored procedures the middle tier.&amp;#160; I have also not found anything quite like T-SQL for querying and analyzing data.&amp;#160; The ability to twist and munge and aggregate your data in a single statement of sub-queries (or better yet, &lt;a href="http://dynamicsgpgeek.blogspot.com/2010/01/common-table-expressions-dispensing.html" target="_blank"&gt;Common Table Expressions&lt;/a&gt;)&lt;/p&gt;  &lt;p&gt;So one day soon will T-SQL be standing among the assemblers and punch card readers in the dusty closet of computer history? Or will the it continue to have a place in our data toolbox?&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-4715982584925524453?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/4715982584925524453/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/02/wither-sql-developer-is-t-sql-next.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4715982584925524453'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4715982584925524453'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/02/wither-sql-developer-is-t-sql-next.html' title='Wither the SQL Developer? Is T-SQL the Next Assembly Language?'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-194359428871062654</id><published>2010-01-18T18:14:00.000-08:00</published><updated>2010-01-20T10:53:13.713-08:00</updated><title type='text'>Common Table Expressions – Dispensing with the Sub-Query</title><content type='html'>&lt;p&gt;The Common Table Expression (CTE) is a construct that was introduced in SQL Server 2005. It allows you to define a SELECT statement outside of your main query and then reference it within the query. It’s a great replacement for sub-queries, which can be difficult to debug and maintain, especially once their nested. You can think of CTEs as SQL views that exist only within the scope of a particular SQL statement.&lt;/p&gt;  &lt;p&gt;Let’s start with an query example similar to one we were asked to do recently. Let’s imagine you’re asked to write an inventory analysis report that requires the following columns:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Item ID &lt;/li&gt;    &lt;li&gt;Item Description &lt;/li&gt;    &lt;li&gt;Item Class &lt;/li&gt;    &lt;li&gt;Standard Cost &lt;/li&gt;    &lt;li&gt;Quantity On Hand &lt;/li&gt;    &lt;li&gt;Count Sold – The total number of items sold. &lt;/li&gt;    &lt;li&gt;Average Unit Price – The average unit price that the &lt;/li&gt;    &lt;li&gt;Unique Customers Count – The count if individual customers who have purchased the item &lt;/li&gt;    &lt;li&gt;Percentage of Customers – The percentage of all customers who have purchased the item &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The report will have start and end date range parameters (Quantity On Hand will be the current regardless of the date range.&lt;/p&gt;  &lt;p&gt;Item ID, Item Description, Item Class and Standard Cost come from the item master table. Quantity On Hand comes from the item quantity master table. Count Sold, Average Unit Price, Count of Unique Customers and Percentage of Customers come from a combination of the Sales Order and Customer Master tables.&lt;/p&gt;  &lt;p&gt;Now I suspect there may be a way to do this without sub-queries using aggregates and CASE statements but that’s a challenge for another day. When solving a problem I like to break it into manageable chunks. In this case I’d looking at breaking this into two pieces:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Returning the core item information, Item ID, Item Description, Standard Cost and Quantity On Hand. &lt;/li&gt;    &lt;li&gt;Returning the sales information, Count Sold and Average Unit Price, Unique Customer Count and Percentage of Customers. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Let’s start with the core item info. That query would look as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ItemNumber = ISNULL(ItemMaster.ITEMNMBR, QuantityMaster.ITEMNMBR)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemDescription = ItemMaster.ITEMDESC      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemClass = ItemMaster.ITMCLSCD      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,StandardCost = ItemMaster.STNDCOST      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,QuantityOnHand = ISNULL(QuantityMaster.QTYONHND, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM IV00101 AS ItemMaster      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FULL JOIN IV00102 AS QuantityMaster      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON ItemMaster.ITEMNMBR = QuantityMaster.ITEMNMBR      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WHERE ISNULL(QuantityMaster.RCRDTYPE, 1) = 1&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here we’re joining the item master (IV00101) with the item quantity master (IV00102). A couple of notes:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;We’re using a FULL JOIN to ensure we pick up all items. I have found examples where there are records in the item master or quantity master but not a corresponding record in the other. &lt;/li&gt;    &lt;li&gt;We’re restricting the quantity master records to a record type (RCRDTYPE) of 1, which only returns quantities that apply to all sites. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Next we’ll look at the statistics. The query would look as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ItemNumber = SOPLineItems.ITEMNMBR      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CountSold = SUM(SOPLineItems.QUANTITY)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,AverageUnitPrice =      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN SUM(SOPLineItems.QUANTITY) = 0 THEN 0.00      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE SUM(SOPLineItems.QUANTITY * SOPLineItems.UNITPRCE) / SUM(SOPLineItems.QUANTITY)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerCount = COUNT(DISTINCT CUSTNMBR)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerPercentage = CAST(COUNT(DISTINCT CUSTNMBR) AS float) /      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CAST ((SELECT COUNT(*) FROM RM00101) AS float)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM SOP30300 AS SOPLineItems      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; INNER JOIN SOP30200 AS SOPHeaders      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON SOPLineItems.SOPTYPE = SOPHeaders.SOPTYPE      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND SOPLineItems.SOPNUMBE = SOPHeaders.SOPNUMBE      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WHERE SOPHeaders.SOPTYPE = 3      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND SOPHeaders.GLPOSTDT BETWEEN @StartDate AND @EndDate      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; GROUP BY SOPLineItems.ITEMNMBR&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here we’re pulling the data from SOP line items and SOP headers with a total count from the customer master. A couple of notes:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;We’re using a CASE statement in the Average Unit Price to ensure we don’t get a divide by zero error. &lt;/li&gt;    &lt;li&gt;We’re filtering the rows on the SOP header GL posting date (GLPOSTDT). &lt;/li&gt;    &lt;li&gt;We’re getting the item customer count using the COUNT(DISTINCT ) construct. &lt;/li&gt;    &lt;li&gt;While getting the customer percentage we’re explicitly casting the counts as floats so that it doesn’t perform integer division and only return 0 and 1. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;So traditionally, we would combine these together as two sub-queries as below:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ItemNumber = Items.ItemNumber      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemClass = Items.ItemClass      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemDescription = Items.ItemDescription      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,StandardCost = Items.StandardCost      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,QuantityOnHand = Items.QuantityOnHand      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CountSold = ISNULL(ItemStatistics.CountSold, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,AverageUnitPrice = ISNULL(ItemStatistics.AverageUnitPrice, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerCount = ISNULL(ItemStatistics.CustomerCount, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerPercentage = ISNULL(ItemStatistics.CustomerPercentage, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ItemNumber = ISNULL(ItemMaster.ITEMNMBR, QuantityMaster.ITEMNMBR)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemDescription = ItemMaster.ITEMDESC      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemClass = ItemMaster.ITMCLSCD      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,StandardCost = ItemMaster.STNDCOST      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,QuantityOnHand = ISNULL(QuantityMaster.QTYONHND, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM IV00101 AS ItemMaster      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FULL JOIN IV00102 AS QuantityMaster      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON ItemMaster.ITEMNMBR = QuantityMaster.ITEMNMBR      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE ISNULL(QuantityMaster.RCRDTYPE, 1) = 1) AS Items      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; LEFT JOIN (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ItemNumber = SOPLineItems.ITEMNMBR      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CountSold = SUM(SOPLineItems.QUANTITY)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,AverageUnitPrice =      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN SUM(SOPLineItems.QUANTITY) = 0 THEN 0.00      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE SUM(SOPLineItems.QUANTITY * SOPLineItems.UNITPRCE) / SUM(SOPLineItems.QUANTITY)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerCount = COUNT(DISTINCT CUSTNMBR)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerPercentage = CAST(COUNT(DISTINCT CUSTNMBR) AS float) /      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CAST ((SELECT COUNT(*) FROM RM00101) AS float)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM SOP30300 AS SOPLineItems      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; INNER JOIN SOP30200 AS SOPHeaders      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON SOPLineItems.SOPTYPE = SOPHeaders.SOPTYPE      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND SOPLineItems.SOPNUMBE = SOPHeaders.SOPNUMBE      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE SOPHeaders.SOPTYPE = 3      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND SOPHeaders.GLPOSTDT BETWEEN @StartDate AND @EndDate      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GROUP BY SOPLineItems.ITEMNMBR) AS ItemStatistics      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON Items.ItemNumber = ItemStatistics.ItemNumber&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Although this works, it’s starting to get a little difficult to read. If there were additional sub-queries, particularly nested ones, it would be much harder to follow and debug. If we’re using CTEs we would create the query below:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;-- Items common table expression returns item list with quantities     &lt;br /&gt;-- for all sites.      &lt;br /&gt;WITH Items      &lt;br /&gt;AS (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ItemNumber = ISNULL(ItemMaster.ITEMNMBR, QuantityMaster.ITEMNMBR)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemDescription = ItemMaster.ITEMDESC      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemClass = ItemMaster.ITMCLSCD      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,StandardCost = ItemMaster.STNDCOST      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,QuantityOnHand = ISNULL(QuantityMaster.QTYONHND, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM IV00101 AS ItemMaster      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FULL JOIN IV00102 AS QuantityMaster      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON ItemMaster.ITEMNMBR = QuantityMaster.ITEMNMBR      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE ISNULL(QuantityMaster.RCRDTYPE, 1) = 1      &lt;br /&gt;), &lt;/p&gt;    &lt;p&gt;-- Item statistics common table expression returns the relevant     &lt;br /&gt;-- statistics for each item.      &lt;br /&gt;ItemStatistics      &lt;br /&gt;AS (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ItemNumber = SOPLineItems.ITEMNMBR      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CountSold = SUM(SOPLineItems.QUANTITY)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,AverageUnitPrice =      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN SUM(SOPLineItems.QUANTITY) = 0 THEN 0.00      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE SUM(SOPLineItems.QUANTITY * SOPLineItems.UNITPRCE) / SUM(SOPLineItems.QUANTITY)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerCount = COUNT(DISTINCT CUSTNMBR)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerPercentage = CAST(COUNT(DISTINCT CUSTNMBR) AS float) /      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CAST ((SELECT COUNT(*) FROM RM00101) AS float)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM SOP30300 AS SOPLineItems      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; INNER JOIN SOP30200 AS SOPHeaders      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON SOPLineItems.SOPTYPE = SOPHeaders.SOPTYPE      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND SOPLineItems.SOPNUMBE = SOPHeaders.SOPNUMBE      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE SOPHeaders.SOPTYPE = 3      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND SOPHeaders.GLPOSTDT BETWEEN @StartDate AND @EndDate      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GROUP BY SOPLineItems.ITEMNMBR      &lt;br /&gt;) &lt;/p&gt;    &lt;p&gt;-- Final query joins the two CTEs together.     &lt;br /&gt;SELECT      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ItemNumber = Items.ItemNumber      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemClass = Items.ItemClass      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,ItemDescription = Items.ItemDescription      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,StandardCost = Items.StandardCost      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,QuantityOnHand = Items.QuantityOnHand      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CountSold = ISNULL(ItemStatistics.CountSold, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,AverageUnitPrice = ISNULL(ItemStatistics.AverageUnitPrice, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerCount = ISNULL(ItemStatistics.CustomerCount, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,CustomerPercentage = ISNULL(ItemStatistics.CustomerPercentage, 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM Items      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; LEFT JOIN ItemStatistics      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON Items.ItemNumber = ItemStatistics.ItemNumber&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;In the query above we have two common table expressions, Items and ItemStatistics. We declare the first CTE using the WITH clause. Subsequent CTEs are separated with a comma. Note that all CTEs must be declared at the beginning of the query. The final query simply joins the two CTEs together&lt;/p&gt;  &lt;p&gt;The biggest advantage to CTEs is their readability, manageability and the ease of troubleshooting. It’s much easier to break the query down and troubleshoot its individual components than to do the same with nested sub-queries. Other things to note about CTEs:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;A CTE can reference other CTEs defined above it.&lt;/li&gt;    &lt;li&gt;A query can join to a CTE multiple times. i.e., wherever you might use the same sub-query multiple times you can now define it once in a CTE and simply reference the CTE multiple times.&lt;/li&gt;    &lt;li&gt;You can reference parameters and variables in a CTE and thus optimize your queries by filtering out records prior to joining them in your main query.&lt;/li&gt;    &lt;li&gt;If you have any statements above a CTE, such as DECLARE or SET statements in a stored procedure, you must terminate those prior statements using a semicolon.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;So let us bid farewell to the much vaunted and occasionally overused sub-query and hello to our new friend, the Common Table Expression.&lt;/p&gt;  &lt;p&gt;Next up, the Recursive Common Table Expression.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-194359428871062654?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/194359428871062654/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/common-table-expressions-dispensing.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/194359428871062654'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/194359428871062654'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/common-table-expressions-dispensing.html' title='Common Table Expressions – Dispensing with the Sub-Query'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-755047698419716709</id><published>2010-01-14T19:00:00.000-08:00</published><updated>2010-01-14T19:00:00.566-08:00</updated><title type='text'>Excellent Consulting Tips Article</title><content type='html'>&lt;p&gt;I’m veering off the technical path here, but I came across this great article on the things NOT to do as a consultant.&lt;/p&gt;&lt;p&gt;&lt;a href="http://blogs.techrepublic.com.com/10things/?p=1290&amp;amp;tag=nl.e550" target="_blank"&gt;10 things you should never do on a consulting job&lt;/a&gt; (&lt;a href="http://blogs.techrepublic.com.com/10things/?p=1290&amp;amp;tag=nl.e550"&gt;http://blogs.techrepublic.com.com/10things/?p=1290&amp;amp;tag=nl.e550&lt;/a&gt;)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-755047698419716709?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/755047698419716709/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/excellent-consulting-tips-article.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/755047698419716709'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/755047698419716709'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/excellent-consulting-tips-article.html' title='Excellent Consulting Tips Article'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-7173192880496597890</id><published>2010-01-13T18:52:00.001-08:00</published><updated>2010-01-15T14:25:44.474-08:00</updated><title type='text'>My Adventures using eConnect Stored Procedures – Part 2, Error Handling</title><content type='html'>&lt;p&gt;I’m a big believer in doing things in a consistent fashion, be it formatting SQL code or loading the dishwasher.&amp;#160; To that end, I like to handle errors consistently in my applications and that means using C#’s standard method using Try/Catch.&amp;#160; &lt;/p&gt;  &lt;p&gt;The eConnect stored procedures don’t raise an error if one occurs but rather, as mentioned in &lt;a href="http://dynamicsgpgeek.blogspot.com/2010/01/my-adventures-using-econnect-stored.html" target="_blank"&gt;Part 1&lt;/a&gt;, pass back error information in two output parameters, @O_iErrorState, an integer and @oErrString a 255 character varchar.&amp;#160; &lt;/p&gt;  &lt;p&gt;Now unfortunately, neither of these variables on their own provide much interesting information.&amp;#160; @O_iErrorState returns a single integer representing an error.&amp;#160; @oErrString sends a list of integers, separated by spaces, if more than one error is occurs.&amp;#160; So where do I get the text that corresponds to the numbers?&lt;/p&gt;  &lt;p&gt;The answer is in the taErrorCode table in the DYNAMICS database.&amp;#160; The error codes returned by the eConnect stored procedures correspond to the values in the ErrorCode column.&amp;#160; Using that you can return the stored procedure the error code relates to from the SourceProc column and the error description from the ErrorDesc column.&lt;/p&gt;  &lt;p&gt;So that solves the first part of my problem, which is how to retrieve readable text from the error codes, but how do I then go and throw an exception with all of the error information instead of relying on the output parameters?&amp;#160; The first step is to consolidate the information for one or more errors into a single string.&amp;#160; For this I created a user defined scalar function that takes an error code list, as returned by the @oErrString output parameter and returns a single string as follows (best read by cutting and pasting into Management Studio):&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;CREATE FUNCTION [dbo].[fnvGeteConnectErrorMessage]      &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @eConnectErrorString nvarchar(255)       &lt;br /&gt;)       &lt;br /&gt;RETURNS nvarchar(max)       &lt;br /&gt;AS       &lt;br /&gt;BEGIN       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @ErrorString nvarchar(255)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @ErrorMessage nvarchar(max)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @DelimiterIndex int       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @CurrentErrorCode nvarchar(10)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @CRLF nvarchar(2) &lt;/p&gt;    &lt;p&gt;--Initialize variables      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SET @CRLF = CHAR(13) + CHAR(10)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SET @ErrorString = @eConnectErrorString       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SET @ErrorMessage = 'The following error(s) were returned:' + @CRLF + @CRLF       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SET @ErrorString = LTRIM(RTRIM(@ErrorString)) &lt;/p&gt;    &lt;p&gt;--If no errors then return empty string      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; IF LEN(@ErrorString) = 0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; BEGIN       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; RETURN N''       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; END &lt;/p&gt;    &lt;p&gt;--Find first delimiter      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SET @DelimiterIndex = CHARINDEX(' ', @ErrorString, 0)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; IF @DelimiterIndex = 0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; BEGIN       &lt;br /&gt;--If no delimiter, return entire string       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SET @CurrentErrorCode = @ErrorString       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ELSE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; BEGIN       &lt;br /&gt;--If delimiter found, set current error code to first error code in string       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SET @CurrentErrorCode = SUBSTRING(@ErrorString, 1, (@DelimiterIndex - 1))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; END &lt;/p&gt;    &lt;p&gt;--Loop until there are no error codes remaining      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WHILE LEN(@CurrentErrorCode) &amp;gt; 0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; BEGIN       &lt;br /&gt;--Return error stored procedure and message and append to error message to be returned       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; @ErrorMessage = @ErrorMessage + 'Source procedure - ' + ISNULL(SourceProc, '')       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + @CRLF + 'Error - ' + ISNULL(ErrorDesc, '') + @CRLF + @CRLF       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM DYNAMICS.dbo.taErrorCode       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE ErrorCode = @CurrentErrorCode &lt;/p&gt;    &lt;p&gt;--Remove current error code from string and get next error code      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SET @ErrorString = RTRIM(LTRIM(SUBSTRING(@ErrorString, (LEN(@CurrentErrorCode) + 1), (LEN(@ErrorString) - LEN(@CurrentErrorCode)))))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SET @DelimiterIndex = CHARINDEX(' ', @ErrorString, 0)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; IF @DelimiterIndex = 0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BEGIN       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SET @CurrentErrorCode = @ErrorString       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BEGIN       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SET @CurrentErrorCode = SUBSTRING(@ErrorString, 1, (@DelimiterIndex - 1))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; END &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; -- Return the result of the function      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; RETURN @ErrorMessage       &lt;br /&gt;END&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;What this function is ultimately doing is taking the space delimited list of error codes, looping through them, retrieving the source procedure and error description, appending it to the overall error message and then returning the whole thing.&lt;/p&gt;  &lt;p&gt;Now that I can return a consolidated error string, how do I throw an error?&amp;#160; I handled this by creating a wrapper stored procedure.&amp;#160; This not only allows me to handle errors the way I want, but also have a stored procedure with only the parameters I need in my code, with readable names.&amp;#160; Below is the wrapper stored procedure I created to insert a GL transaction header:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;CREATE PROCEDURE [dbo].[spvGLInsertGPGLHeader]      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @BatchNumber char(15)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ,@JournalEntry int       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ,@JournalReference char(30)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ,@TransactionDate datetime       &lt;br /&gt;AS &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @RC int      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @ErrorState int       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @ErrorString varchar(255)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @ErrorMessage nvarchar(max) &lt;/p&gt;    &lt;p&gt;--Execute the taGLTransactionHeaderInsert eConnect stored      &lt;br /&gt;--procedure with passed parameters and required defaults.       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; EXECUTE @RC = [PHI2].[dbo].[taGLTransactionHeaderInsert]       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; @I_vBACHNUMB = @BatchNumber       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,@I_vJRNENTRY = @JournalEntry       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,@I_vREFRENCE = @JournalReference       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,@I_vTRXDATE = @TransactionDate       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,@I_vTRXTYPE =0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,@I_vSERIES = 2       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,@I_vRequesterTrx = 1       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,@O_iErrorState = @ErrorState OUTPUT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,@oErrString = @ErrorString OUTPUT &lt;/p&gt;    &lt;p&gt;--If any errors are returned, get the consolidated error message      &lt;br /&gt;--and raise a SQL error.       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; IF LEN(@ErrorString) &amp;lt;&amp;gt; 0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; BEGIN       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SET @ErrorMessage = dbo.fnvGeteConnectErrorMessage(@ErrorString)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; RAISERROR (@ErrorMessage, 18, 1) &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; RETURN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; END&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;As you can see, we’re using TSQL’s RAISERROR (doesn’t the missing “E” in the middle bug you a little) to throw an error back to your calling code.&amp;#160; Also note that we have a much cleaner stored procedure to reference from our calling application.&lt;/p&gt;  &lt;p&gt;Next up, calling this from an ADO.NET table adaptor in a Dynamics GP .NET add-in.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-7173192880496597890?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/7173192880496597890/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/my-adventures-using-econnect-stored_13.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7173192880496597890'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7173192880496597890'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/my-adventures-using-econnect-stored_13.html' title='My Adventures using eConnect Stored Procedures – Part 2, Error Handling'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-1979412657145637702</id><published>2010-01-12T20:23:00.000-08:00</published><updated>2010-01-13T19:06:37.462-08:00</updated><title type='text'>My Adventures using eConnect Stored Procedures – Part 1</title><content type='html'>&lt;p&gt;OK. So over a month ago I talked about doing an integration using the eConnect stored procedures directly instead of the COM+ or Message Queue interfaces. Well, I’m happy to report that the first one is completed. It’s a GL integration with AA (Analytical Accounting) transactions from an Excel file. Each row represents a single journal entry distribution and has two columns with AA dimensions. Each AA column represents a different dimension with a code for each that is allocated 100% of the distribution amount.&lt;/p&gt;  &lt;p&gt;As I noted in the previous post, each eConnect XML node represents a single stored procedure of the same name. In this integration we’re using the following nodes:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;taGLTransactionHeaderInsert &lt;/li&gt;    &lt;li&gt;taGLTransactionLineInsert &lt;/li&gt;    &lt;li&gt;taAnalyticsDistribution &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Each of these stored procedures has a set of parameters that matches the parameters of the XML node and follows the same rules in terms of defaults and whether it’s required or not. In addition there are two output parameters at the end of the parameters list, @O_iErrorState, an integer and @oErrString a 255 character varchar. These parameters return information an any errors that occurred in processing.&lt;/p&gt;  &lt;p&gt;In addition to the stored procedures that represent the eConnect XML nodes, there was an additional stored procedure, taGetNextJournalEntry, I used to return the next journal entry number. This stored procedure has the following three parameters:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;@I_vInc_Dec – An integer that is either 1 or 0 to indicate whether to increment or decrement the number (in case you’re rolling back a transaction)&lt;/li&gt;    &lt;li&gt;@O_vJournalEntryNumber – A 13 character output parameter that returns the next journal entry number.&lt;/li&gt;    &lt;li&gt;@O_iErrorState – An integer output parameter that returns an error number or 0 if no errors occurred.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;A lesson I learned was the importance of running the stored procedures in the correct order. My gut told me to create the header first, then the transaction lines and AA distributions. My gut was wrong.&lt;/p&gt;  &lt;p&gt;Actually after creating the header and then the transaction lines I was mostly OK. The transactions did get created with the proper distributions. The only clue that something was wrong was that the batch transaction count and total was incorrect. However, after adding the AA logic I got no AA distributions.&lt;/p&gt;  &lt;p&gt;Integrating AA has never failed to frustrate me. A couple of hours of foul language and troubleshooting later I ended up writing a little test app that wrote added the transactions through the COM+ interface. After running a SQL trace in SQL Server Profiler, I discovered that the transaction header insert stored procedure needs to be run last, not first. Once the calls were rearranged all was happy!&lt;/p&gt;  &lt;p&gt;What did I learn? When you avoid the COM+/XML parsing/DTC overhead it’s incredibly fast. That you should have a test application ready to create the transactions using the COM+ interface to determine the correct stored procedure order. That you’ll need to handle transactions in your own code instead of relying on the COM+ object and DTC. And that it’s really not that hard to do and a good option if you have direct access to the database.&lt;/p&gt;  &lt;p&gt;In future posts I’ll talk about some of the other things I did in this integration including &lt;a href="http://dynamicsgpgeek.blogspot.com/2010/01/my-adventures-using-econnect-stored_13.html" target="_blank"&gt;handling the errors returned from the eConnect stored procedures&lt;/a&gt; and connecting to the database using the end-user’s GP credentials.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-1979412657145637702?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/1979412657145637702/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/my-adventures-using-econnect-stored.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/1979412657145637702'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/1979412657145637702'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/my-adventures-using-econnect-stored.html' title='My Adventures using eConnect Stored Procedures – Part 1'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-7747816328906345775</id><published>2010-01-12T13:10:00.001-08:00</published><updated>2010-01-12T13:10:58.989-08:00</updated><title type='text'>SSRS Expressions Tips &amp; Tricks</title><content type='html'>&lt;p&gt;Wow!&amp;#160; Time flies when you’re having fun.&amp;#160; Anyway, between taking some time off for the holidays, preparing to take time off for the holidays and digging out of the email from taking time off for the holidays, I guess I’ve neglected the blog a bit.&lt;/p&gt;  &lt;p&gt;This will just be a short post.&amp;#160; A colleague of mine, Liz Pfahler, passed this link along to me.&amp;#160; It’s a good comprehensive list of tips and tricks for SSRS expressions.&amp;#160; Everything from how to set alternating row colors, to returning the first or last day of the month.&amp;#160; &lt;/p&gt;  &lt;p&gt;There’s a few things I’ve used in the past but there’s definitely some new ones and it’s great to have them all in one place.&lt;/p&gt;  &lt;p&gt;Check it out.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://www.sqlservercentral.com/articles/Reporting+Services+(SSRS)/67660/"&gt;http://www.sqlservercentral.com/articles/Reporting+Services+(SSRS)/67660/&lt;/a&gt;&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-7747816328906345775?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/7747816328906345775/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/ssrs-expressions-tips-tricks.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7747816328906345775'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7747816328906345775'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2010/01/ssrs-expressions-tips-tricks.html' title='SSRS Expressions Tips &amp;amp; Tricks'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-976863363310855407</id><published>2009-11-19T12:26:00.001-08:00</published><updated>2009-11-19T12:26:27.774-08:00</updated><title type='text'>eConnect Message Queue vs. COM+ vs. Stored Procedures</title><content type='html'>&lt;p&gt;A few months ago I wrote about how, for various reasons, I was fed up with the Dynamics GP Web Services and was moving towards writing my own web services working against eConnect. (&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/06/econnect-custom-web-service-vs-dynamics.html" target="_blank"&gt;Click here for that article&lt;/a&gt;)&lt;/p&gt;  &lt;p&gt;Now the reality is that most of the integrations I write are small Windows Forms or intranet applications run within the local LAN and can directly access the SQL server.&amp;#160; I also wondered about writing GP Add-Ins using the Visual Studio Tools for Dynamics GP.&amp;#160; &lt;/p&gt;  &lt;p&gt;In either of these scenarios, not only do I not see the sense of using the Dynamics GP Web Services, but of even using the two most documented methods of accessing eConnect using the COM+ or Message Queue interfaces.&amp;#160; I’ve started looking at using the base stored procedures directly in the database.&amp;#160; &lt;/p&gt;  &lt;p&gt;Since each XML node maps one to one to a stored procedure of the same name, I’m able to just use the schema documentation to see what the various parameters mean.&amp;#160; I haven’t put anything into production yet with this, but I’ll let you know how it goes.&lt;/p&gt;  &lt;p&gt;Anyone else use eConnect this way?&amp;#160; Let me know.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-976863363310855407?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/976863363310855407/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/11/econnect-message-queue-vs-com-vs-stored.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/976863363310855407'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/976863363310855407'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/11/econnect-message-queue-vs-com-vs-stored.html' title='eConnect Message Queue vs. COM+ vs. Stored Procedures'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-4781953577937887884</id><published>2009-11-01T17:37:00.001-08:00</published><updated>2009-11-01T18:37:23.640-08:00</updated><title type='text'>Accessing the Dynamics GP Configuration File (Dynamics.exe.config) from a Visual Studio Tools for Dynamics GP Add-In – Part 3</title><content type='html'>&lt;p&gt;This is Part 3 of a three part article on working with the Dynamics.exe.config .NET configuration file.&amp;#160; with the following articles:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/10/accessing-dynamicsexeconfig.html" target="_blank"&gt;Part 1 – Creating a configuration handler for a custom configuration section.&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/11/accessing-dynamics-gp-configuration.html" target="_blank"&gt;Part 2 – Testing the configuration handler.&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;Part 3 – Using the configuration handler in a Visual Studio Tools for Dynamics GP Add-In &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;a href="http://sites.google.com/site/dynamicsgpgeek/code-samples/HelloGPConfiguration.zip?attredirects=0&amp;amp;d=1" target="_blank"&gt;[Click here for the Visual Studio solution used in these articles]&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;So finally, in this part, we’ll put it all together and demonstrate how to access the Dynamics.exe.config configuration from a Visual Studio Tools for Dynamics GP add-in.&amp;#160; For this, we’ll create a simple “Hello Configuration” add-in.&amp;#160; We’ll attach it to the Additional menu of the SOP Batch Entry form as we might a SOP integration.&lt;/p&gt;  &lt;p&gt;There’s really only two parts of this add-in:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Initialization where we verify the existence of the configuration section and add it if it’s missing and add a menu item and associated event handler to the Additional menu of the SOP Batch Entry form.&lt;/li&gt;    &lt;li&gt;Event handler, that in this case simply displays the “Hello Configuration” caption, as stored in the configuration section, and the hypothetical import file name from the same section.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Thus the code consists of a single class file in the HelloConfigurationAddIn project of the solution linked to above.&amp;#160; The project was created using the Microsoft Dynamics GP Add-In template.&amp;#160; A reference is added to the GPAddInConfiguration project.&amp;#160; The entire code for the add-in is as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;using System.Configuration;     &lt;br /&gt;using GPAddInConfiguration; &lt;/p&gt;    &lt;p&gt;namespace HelloConfigurationAddIn     &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public class GPAddIn : IDexterityAddIn      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; public void Initialize()      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Check to see if the section exists and if not create it.      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.VerifyConfigurationSection(); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Add to Additional menu for SOP batch form.     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Dynamics.Forms.SopBatchEntry.AddMenuHandler(HelloConfigurationEvent,&amp;quot;Hello Configuration&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; } &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; //&amp;#160; Verifies the existence of the section and adds it if missing.     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; void VerifyConfigurationSection()      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // If section doesn't exist create it.      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (ConfigurationManager.GetSection(&amp;quot;GPAddIn&amp;quot;) == null)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Get the current configuration file for writing.      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Create the new section.     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GPAddInSectionHandler section = new GPAddInSectionHandler();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; config.Sections.Add(&amp;quot;GPAddIn&amp;quot;, section);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; section.SectionInformation.ForceSave = true; &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Save the section     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; config.Save(ConfigurationSaveMode.Full); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ConfigurationManager.RefreshSection(&amp;quot;GPAddIn&amp;quot;);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; } &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Script to handle menu entry callbacks     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; void HelloConfigurationEvent(object sender, EventArgs e)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GPAddInSectionHandler section = (GPAddInSectionHandler)ConfigurationManager.GetSection(&amp;quot;GPAddIn&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MessageBox.Show(section.HelloConfigurationCaption + &amp;quot;\n\nImport File:&amp;#160; &amp;quot; + section.ImportFileName, &amp;quot;Hello Configuration&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The Initialize method calls the VerifyConfigurationSection function that checks for the existence of the section in the Dynamics.exe.config configuration, and if missing, adds it with the default values.&amp;#160; Additionally, it adds a menu handler for the Additional menu of the SOP Batch Entry form, passing the HelloConfigurationEvent function as the event handler.&lt;/p&gt;  &lt;p&gt;The HelloConfigurationEvent simply opens the GPAddIn section of the configuration and displays the HelloConfigurationCaption and ImportFileName values in a message box.&lt;/p&gt;  &lt;p&gt;To properly deploy the add-in you need to copy the HelloConfigurationAddIn assembly DLL to the GP AddIns folder as you would any add-in.&amp;#160; However, you also need to deploy the GPAddInConfiguration assembly to the GP folder that contains the Dynamics.exe.config file.&amp;#160; Configuration handler assemblies must reside in the same folder as the host application.&lt;/p&gt;  &lt;p&gt;So give it a whirl and see how changes to the values in the configuration file show up in the message box.&amp;#160; Note that in this case, changes don’t show up until you restart the application. &lt;/p&gt;  &lt;p&gt;I hope these posts have inspired you to make use of the power of the configuration file in your Dynamics GP add-ins.&amp;#160; There’s certainly much more you can do once you dig into them.&amp;#160; Let me know what you find.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-4781953577937887884?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/4781953577937887884/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/11/accessing-dynamics-gp-configuration_01.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4781953577937887884'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4781953577937887884'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/11/accessing-dynamics-gp-configuration_01.html' title='Accessing the Dynamics GP Configuration File (Dynamics.exe.config) from a Visual Studio Tools for Dynamics GP Add-In – Part 3'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-8029864522860144589</id><published>2009-11-01T16:16:00.001-08:00</published><updated>2009-11-01T18:39:16.298-08:00</updated><title type='text'>Accessing the Dynamics GP Configuration File (Dynamics.exe.config) from a Visual Studio Tools for Dynamics GP Add-In – Part 2</title><content type='html'>&lt;p&gt;This is Part 2 of a three part article on working with the Dynamics.exe.config .NET configuration file.&amp;#160; with the following articles:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/10/accessing-dynamicsexeconfig.html" target="_blank"&gt;Part 1 – Creating a configuration handler for a custom configuration section.&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;Part 2 – Testing the configuration handler. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/11/accessing-dynamics-gp-configuration_01.html" target="_blank"&gt;Part 3 – Using the configuration handler in a Visual Studio Tools for Dynamics GP Add-In.&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;a href="http://sites.google.com/site/dynamicsgpgeek/code-samples/HelloGPConfiguration.zip?attredirects=0&amp;amp;d=1" target="_blank"&gt;[Click here for the Visual Studio solution used in these articles]&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Now that we’ve created a configuration section handler, let’s create a little console application to test it out and see some of the things we can do with it.&amp;#160; Note that we’re working with the section handler independently of Dynamics GP.&amp;#160; Section handlers are not tied to a particular applications configuration file, but can be referenced in any application.&lt;/p&gt;  &lt;p&gt;In this test we’ll do the following:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Attempt to get the section, and if it doesn’t exist create it. &lt;/li&gt;    &lt;li&gt;Attempt to update the configuration settings on a section object retrieved using the ConfigurationManager objects GetSection object. &lt;/li&gt;    &lt;li&gt;Show how to update and save changes to the section object. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;This application is the ConfigurationSectionTester project in the solution.&amp;#160; It is a simple console application and references the System.configuration and the GPAddInConfiguration project assembly.&amp;#160; Note that the configuration assembly must be deployed in the same folder as the calling application and it’s corresponding configuration file.&lt;/p&gt;  &lt;p&gt;I’ve created static functions to perform each of the tasks listed above and call them from the Main function so that it looks as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;static void Main(string[] args)      &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; verifyAndGetSection();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; attemptUpdateOfReadonlySection();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; updateSection(); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Hit Enter to continue...&amp;quot;);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.ReadLine();       &lt;br /&gt;} &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;So let’s look at each of the functions individually.&amp;#160; First the verifyAndGetSection function.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;private static void verifyAndGetSection()      &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; GPAddInSectionHandler section; &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;----------&amp;quot;);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Getting or creating section&amp;quot;); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; // Attempt to get section from the static ConfigurationManager object.      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; section = (GPAddInSectionHandler)ConfigurationManager.GetSection(&amp;quot;GPAddIn&amp;quot;); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; // If config section doesn't exist, create the section entry      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // in &amp;lt;configSections&amp;gt; and the       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // related target section in &amp;lt;configuration&amp;gt;.       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; if (section == null)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Open current configuration file for writing. &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; section = new GPAddInSectionHandler();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; config.Sections.Add(&amp;quot;GPAddIn&amp;quot;, section);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; section.SectionInformation.ForceSave = true;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; config.Save(ConfigurationSaveMode.Full);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ConfigurationManager.RefreshSection(&amp;quot;GPAddIn&amp;quot;); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;New section, {0}, created&amp;quot;, section.SectionInformation.Name);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; else       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Section {0} opened.&amp;quot;, section.SectionInformation.Name);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Hello Configuration Caption - {0}&amp;quot;, section.HelloConfigurationCaption);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Import File Name - {0}&amp;quot;, section.ImportFileName);       &lt;br /&gt;} &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;In this function, we first attempt to retrieve the custom section by calling the ConfigurationManager object’s static GetSection method, passing it the section’s name.&amp;#160; If successful, this returns a read-only copy of the section.&amp;#160; If unsuccessful, it returns null (or Nothing in VB.NET).&lt;/p&gt;  &lt;p&gt;If the returned section is null then we open a writeable copy of the configuration using the static OpenExeConfiguration method of the ConfigurationManager object.&amp;#160; Note that the parameter passed to this method indicates whether we’re getting the application or user specific configuration file.&lt;/p&gt;  &lt;p&gt;Once we have the configuration object, we can add the new section to the Sections collection, mark it to force a save, and then save the configuration.&amp;#160; Note that all of this code works even if a configuration file doesn’t even exist.&amp;#160; Saving the configuration will create a new configuration file if one doesn’t already exist.&lt;/p&gt;  &lt;p&gt;Calling the RefreshSection method forces the new section to be loaded when it is next requested within the application.&amp;#160; Otherwise attempting to load the section using the GetSection method prior to restarting the application will return a null object.&lt;/p&gt;  &lt;p&gt;In the second function, attemptUpdateOfReadonlySection, we’ll show what happens if you attempt to update a read-only section object.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;private static void attemptUpdateOfReadonlySection()      &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;----------&amp;quot;);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Attempting to update a readonly section.&amp;quot;); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; // Getting a section using GetSection returns a read-only section.      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; GPAddInSectionHandler section = (GPAddInSectionHandler)ConfigurationManager.GetSection(&amp;quot;GPAddIn&amp;quot;); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; try      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; section.ImportFileName = &amp;quot;NewFileName.txt&amp;quot;;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; catch (Exception ex)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Error setting section attribute - {0}&amp;quot;, ex.Message);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;} &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here, we again attempt to retrieve the section using the ConfigurationManager objects static GetSection method.&amp;#160; GetSection returns a read-only copy of the section.&amp;#160; If you attempt to set any of the properties it will throw an error.&amp;#160; Note that this is true whether or not you intend to ultimately save the update to disk.&lt;/p&gt;  &lt;p&gt;In the final function, updateSection, we show how to successfully update a sections properties and then save it to disk.&amp;#160; This is very similar to how we created a new section in the verifyAndGetSection function.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;private static void updateSection()      &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // To get a writeable section you open the configuration using OpenExeConfiguration       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // and the return the section from the Sections collection.       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; GPAddInSectionHandler section = (GPAddInSectionHandler)config.Sections[&amp;quot;GPAddIn&amp;quot;]; &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;----------&amp;quot;);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Updating a section.&amp;quot;); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; section.HelloConfigurationCaption = section.HelloConfigurationCaption + &amp;quot; - Modified&amp;quot;;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; section.ImportFileName = &amp;quot;NewFileName.txt&amp;quot;; &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; // Set ForceSave to true to ensure the section is saved even if not updated      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; section.SectionInformation.ForceSave = true;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; config.Save(ConfigurationSaveMode.Full); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(); &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Hello Configuration Caption - {0}&amp;quot;, section.HelloConfigurationCaption);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;Import File Name - {0}&amp;quot;, section.ImportFileName);       &lt;br /&gt;} &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here we open the configuration using the static OpenExeConfiguration method of the ConfigurationManager object, again, specifying whether we want the application or user specific version of the configuration.&amp;#160; We then retrieve the section from the Sections collection rather than using the GetSection method.&amp;#160; This gives us a writeable version of the section.&amp;#160; Once we’ve updated the section we then use the Save method of its parent configuration to save it to disk.&lt;/p&gt;  &lt;p&gt;When testing this, run it from the command line rather than from the Visual Studio IDE.&amp;#160; When running it from the IDE, the initial state of the configuration file (i.e. no file), is restored each time.&lt;/p&gt;  &lt;p&gt;The first time you run this, make sure you should start with no configuration file and your command window should look as follows:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_M9HO4tQKytY/Su42PuSj0TI/AAAAAAAAAEo/V4vIPdV10EI/s1600-h/clip_image001%5B7%5D%5B4%5D.png"&gt;&lt;img title="clip_image001[7]" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="281" alt="clip_image001[7]" src="http://lh5.ggpht.com/_M9HO4tQKytY/Su42P_-_CzI/AAAAAAAAAEs/BOB0O4diIuI/clip_image001%5B7%5D_thumb%5B2%5D.png?imgmax=800" width="546" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Note that first function is creating a new section and setting the values to their defaults.&amp;#160; When you run it a second time you should see the following:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_M9HO4tQKytY/Su42QBbS6FI/AAAAAAAAAEw/6KG4c8md5-U/s1600-h/clip_image001%5B5%5D%5B3%5D.png"&gt;&lt;img title="clip_image001[5]" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="282" alt="clip_image001[5]" src="http://lh3.ggpht.com/_M9HO4tQKytY/Su42QYMDj2I/AAAAAAAAAE0/MkutcBiz-cU/clip_image001%5B5%5D_thumb%5B1%5D.png?imgmax=800" width="551" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Here you’ll note that the section is being opened and showing the section values updated from the defaults in the previous run.&amp;#160; Also note the “The configuration is read only” error message when attempting to update the section returned by the GetSection method.&lt;/p&gt;  &lt;p&gt;Hopefully this has given you some of the basic ways of using successfully using your configuration handler.&amp;#160; In the Part 3, we’ll tie it all together in a Dynamics GP Add-In.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-8029864522860144589?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/8029864522860144589/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/11/accessing-dynamics-gp-configuration.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/8029864522860144589'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/8029864522860144589'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/11/accessing-dynamics-gp-configuration.html' title='Accessing the Dynamics GP Configuration File (Dynamics.exe.config) from a Visual Studio Tools for Dynamics GP Add-In – Part 2'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_M9HO4tQKytY/Su42P_-_CzI/AAAAAAAAAEs/BOB0O4diIuI/s72-c/clip_image001%5B7%5D_thumb%5B2%5D.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-353277981026347983</id><published>2009-10-31T17:31:00.000-07:00</published><updated>2009-11-01T18:47:44.930-08:00</updated><title type='text'>Accessing the Dynamics GP Configuration File (Dynamics.exe.config) from a Visual Studio Tools for Dynamics GP Add-In – Part 1</title><content type='html'>&lt;p&gt;This is Part 1 of a three part article on working with the Dynamics.exe.config .NET configuration file. with the following articles:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Part 1 – Creating a configuration handler for a custom configuration section. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/11/accessing-dynamics-gp-configuration.html" target="_blank"&gt;Part 2 – Testing the configuration handler.&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/11/accessing-dynamics-gp-configuration_01.html" target="_blank"&gt;Part 3 – Using the configuration handler in a Visual Studio Tools for Dynamics GP Add-In&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;a href="http://sites.google.com/site/dynamicsgpgeek/code-samples/HelloGPConfiguration.zip?attredirects=0&amp;amp;d=1" target="_blank"&gt;[Click here for the Visual Studio solution used in these articles]&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;So one of the great things I love about .NET is the configuration file. A more flexible file format than the old .ini file and not the registry (need I say more?). That being said, I’ve tended to rely on the simple AppSettings mechanism making it fairly easy to create and manage your settings in C# or VB.NET projects. &lt;/p&gt;  &lt;p&gt;I love using them for things like default import file paths, remembering user interface settings, connection strings, URLs and such. Now I know there are other ways to store settings, such as in a database table, but sometimes the configuration file is the best tool for the job.&lt;/p&gt;  &lt;p&gt;However, .NET DLLs, for a number of reasons I won’t get into here, don’t inherently have their own configuration files. They must access their parent application’s file. Although you can, in fact, use AppSettings, as long as you modify the host application’s (in this case Dynamics’) configuration file, there are some great reasons to go custom.&amp;#160; And it’s easier than you may think.&lt;/p&gt;  &lt;p&gt;Since a Dynamics GP Add-In is a .NET DLL, the configuration file we have access to is the Dynamics.exe.config file. So how do we use it to store our own settings that can be used by our Add-Ins?&lt;/p&gt;  &lt;p&gt;Step one in this process is to understand the structure and handling of configuration files.&lt;/p&gt;  &lt;p&gt;Configuration files are broken into sections (there are also section groups but we’ll ignore those for this project). A section is simply an XML node off the top level configuration node. There’s really no restrictions on its structure as long as it’s valid XML. In GP 10 there is a single section, shell.&lt;/p&gt;  &lt;p&gt;Each section must have a section definition that includes the assembly that contains the configuration handler object that reads that section. Let’s take a look at the Dynamics.exe.config file.&lt;/p&gt;  &lt;p&gt;&lt;a title="Default Dynamics.exe.config configuration file" href="http://sites.google.com/site/dynamicsgpgeek/code-samples/Default.Dynamics.exe.config?attredirects=0&amp;amp;d=1" target="_blank"&gt;&lt;img title="clip_image001" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="444" alt="clip_image001" src="http://lh4.ggpht.com/_M9HO4tQKytY/Su04YOI2FOI/AAAAAAAAAEY/HWFXgUdd3jM/clip_image001%5B13%5D.png?imgmax=800" width="543" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://sites.google.com/site/dynamicsgpgeek/code-samples/Default.Dynamics.exe.config?attredirects=0&amp;amp;d=1" target="_blank"&gt;[Click here to download the default Dynamics.exe.config configuration file]&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Notice that the first node in the configuration file is the configurationsections node. This is where sections are defined. Notice that here, Dynamics is defining the shell section, which you will note is the next node after the configuration sections node. The section definition consists of the name, which is the name of the top level node for that section, followed by the type, which defines the assembly and handler that reads that section.&lt;/p&gt;  &lt;p&gt;So to add our own section to the configuration file, we simply need to add a section definition and then the corresponding section…oh yeah, and write a configuration section handler assembly.&lt;/p&gt;  &lt;p&gt;In this example, we’re going to create a fairly. It will have a single node, GPAddIn and two properties, HelloConfigurationCaption for our message box, and ImportFileName for our hypothetical file integration. The properties are going to be implemented as attributes of the GPAddIn node, leaving us without the need to handle sub-nodes. &lt;/p&gt;  &lt;p&gt;When all is said and done our modified Dynamics.exe.config file will look like this:&lt;/p&gt;  &lt;p&gt;&lt;a title="Dynamics.exe.config file with custom section" href="http://sites.google.com/site/dynamicsgpgeek/code-samples/Custom.Dynamics.exe.config?attredirects=0&amp;amp;d=1" target="_blank"&gt;&lt;img title="clip_image001[8]" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="238" alt="clip_image001[8]" src="http://lh4.ggpht.com/_M9HO4tQKytY/Su1Ekb798pI/AAAAAAAAAEc/I3FBb4H60qE/clip_image001%5B8%5D%5B4%5D.png?imgmax=800" width="546" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://sites.google.com/site/dynamicsgpgeek/code-samples/Custom.Dynamics.exe.config?attredirects=0&amp;amp;d=1" target="_blank"&gt;[Click here to download the configuration file with the custom section]&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Note that we’ve added a new section definition for the GPAddIn section and the section itself with our two settings as attributes. In the type attribute of the section definition, we have “GPAddInConfiguriation.GPAddInSectionHandler”, the actual class that reads the GPAddIn section, and “GPAddInConfiguration”, the name of the assembly itself.&lt;/p&gt;  &lt;p&gt;So now that we can read the configuration file and define a custom section, how do we write the code to read it?&lt;/p&gt;  &lt;p&gt;So a couple of basics. The section handler class itself is a class that derives from the System.Configuration.ConfigurationSection class. The actual assembly must be a .NET DLL and must be deployed to the same folder &lt;em&gt;as the parent executable&lt;/em&gt;. Note that this means that it is in the Microsoft Dynamics\GP folder, not the AddIns folder that the add-in itself will be deployed to.&lt;/p&gt;  &lt;p&gt;So let’s write some code! First things first. The source code for this solution can be downloaded from &lt;a href="http://sites.google.com/site/dynamicsgpgeek/code-samples/HelloGPConfiguration.zip?attredirects=0&amp;amp;d=1" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;We’re going to start with the configuration handler DLL project, GPAddInConfiguration. This project is a Class Library project and consists of a single C# class file containing a single class, GPAddInSectionHandler, derived from System.Configuration.ConfigurationSection. You will need to reference the System.Configuration assembly in the project.&lt;/p&gt;  &lt;p&gt;The beginning of your class file will look as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;using System;      &lt;br /&gt;using System.Configuration; &lt;/p&gt;    &lt;p&gt;namespace GPAddInConfiguration      &lt;br /&gt;{       &lt;br /&gt;public class GPAddInSectionHandler : ConfigurationSection       &lt;br /&gt;{&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Next we’ll define the constructors:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;// CustomSection constructor.      &lt;br /&gt;public GPAddInSectionHandler()       &lt;br /&gt;{       &lt;br /&gt;} &lt;/p&gt;    &lt;p&gt;public GPAddInSectionHandler(string helloConfigurationCaption, string importFileName)      &lt;br /&gt;{       &lt;br /&gt;HelloConfigurationCaption = helloConfigurationCaption;       &lt;br /&gt;ImportFileName = importFileName;       &lt;br /&gt;} &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;After that we define the configuration attributes. We’re using the declarative method so we don’t need to worry about the configuration property collection or private variables:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;[ConfigurationProperty(&amp;quot;HelloConfigurationCaption&amp;quot;, DefaultValue = &amp;quot;Hello Dynamics GP Configuration&amp;quot;, IsRequired = true)]      &lt;br /&gt;[StringValidator(MinLength = 1, MaxLength = 60)]       &lt;br /&gt;public string HelloConfigurationCaption       &lt;br /&gt;{       &lt;br /&gt;get       &lt;br /&gt;{       &lt;br /&gt;return (string)this[&amp;quot;HelloConfigurationCaption&amp;quot;];       &lt;br /&gt;}       &lt;br /&gt;set       &lt;br /&gt;{       &lt;br /&gt;this[&amp;quot;HelloConfigurationCaption&amp;quot;] = value;       &lt;br /&gt;}       &lt;br /&gt;} &lt;/p&gt;    &lt;p&gt;[ConfigurationProperty(&amp;quot;ImportFileName&amp;quot;, DefaultValue = &amp;quot;importfile.txt&amp;quot;, IsRequired = true)]      &lt;br /&gt;[StringValidator(InvalidCharacters = &amp;quot; ~!@#$%^&amp;amp;*()[]{}/;'\&amp;quot;\\&amp;quot;,       &lt;br /&gt;MinLength = 1, MaxLength = 255)]       &lt;br /&gt;public string ImportFileName       &lt;br /&gt;{       &lt;br /&gt;get       &lt;br /&gt;{       &lt;br /&gt;return (string)this[&amp;quot;ImportFileName&amp;quot;];       &lt;br /&gt;}       &lt;br /&gt;set       &lt;br /&gt;{       &lt;br /&gt;this[&amp;quot;ImportFileName&amp;quot;] = value;       &lt;br /&gt;}       &lt;br /&gt;} &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here we’ve defined the two configuration properties, HelloConfigurationCaption and ImportFileName. Using the ConfigurationProperty attribute we’ve defined their type, default value and whether or not their required without having to write any of the handling code.&lt;/p&gt;  &lt;p&gt;Additionally, using the StringValidator attribute we can define what constitutes a valid value, such as maximum length, and in the case of the ImportFileName, invalid characters.&lt;/p&gt;  &lt;p&gt;And that’s it. Configuration handler completed. Obviously there’s a lot more that you can do such as child elements and even complete custom handling of the section XML, but for a basic replacement of AppSettings, this works quite nicely, and I would argue, makes for a more readable configuration file.&lt;/p&gt;  &lt;p&gt;Hopefully this has demystified the configuration file and will allow you to move beyond the simple AppSettings object. In Part 2, well take our section handler and run it through its paces in a test application.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-353277981026347983?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/353277981026347983/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/10/accessing-dynamicsexeconfig.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/353277981026347983'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/353277981026347983'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/10/accessing-dynamicsexeconfig.html' title='Accessing the Dynamics GP Configuration File (Dynamics.exe.config) from a Visual Studio Tools for Dynamics GP Add-In – Part 1'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_M9HO4tQKytY/Su04YOI2FOI/AAAAAAAAAEY/HWFXgUdd3jM/s72-c/clip_image001%5B13%5D.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-1051036192296239371</id><published>2009-10-30T01:03:00.001-07:00</published><updated>2009-10-30T10:07:36.750-07:00</updated><title type='text'>Error When Running Dynamics GP SQL Reporting Services Trial Balance Summary or Detail Reports</title><content type='html'>&lt;p&gt;This post deals with the following error message:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;The EXECUTE permission was denied on the object 'smGetMsgString', database 'DYNAMICS', schema 'dbo'.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;We recently deployed the Dynamics GP SQL reports to a client.&amp;#160; I think this must be the first client who actually went through all of them because we came across a bit of a bug in the security in the Trial Balance Summary and Detail Reports. &lt;/p&gt;  &lt;p&gt;Now admittedly, we diverted somewhat from the standard install by modifying the shared data sources to use a fixed SQL login rather than integrated security but we dutifully added that login to the rpt_poweruser role in each of the company databases and the DYNAMICS database.&lt;/p&gt;  &lt;p&gt;When the client went to run the Trial Balance Detail report they got the following error:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;a href="http://lh6.ggpht.com/_M9HO4tQKytY/SusdVjvYBUI/AAAAAAAAADw/w4Yu8wot8Fw/s1600-h/image%5B2%5D.png"&gt;&lt;img title="image" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" height="221" alt="image" src="http://lh5.ggpht.com/_M9HO4tQKytY/SusdWO7t6_I/AAAAAAAAAD0/HinOEH77XyI/image_thumb.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;An error occurred during client rendering.&lt;/p&gt;    &lt;p&gt;An error has occurred during report processing.&lt;/p&gt;    &lt;p&gt;Query execution failed for dataset 'dsProc'.&lt;/p&gt;    &lt;p&gt;The EXECUTE permission was denied on the object 'smGetMsgString', database 'DYNAMICS', schema 'dbo'. &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The same error occurred on the Trial Balance Summary report.&amp;#160; In reviewing the permissions on the smGetMsgString stored procedure, I found that only the DYNGRP role int he DYNAMICS database had EXECUTE permissions on it.&amp;#160; After giving the three “rpt_” roles EXECUTE permissions, the report ran fine.&lt;/p&gt;  &lt;p&gt;Has anyone else run across this or similar issues on other reports?&amp;#160; Has Microsoft fixed this in a service pack?&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-1051036192296239371?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/1051036192296239371/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/10/error-when-running-dynamics-gp-sql.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/1051036192296239371'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/1051036192296239371'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/10/error-when-running-dynamics-gp-sql.html' title='Error When Running Dynamics GP SQL Reporting Services Trial Balance Summary or Detail Reports'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_M9HO4tQKytY/SusdWO7t6_I/AAAAAAAAAD0/HinOEH77XyI/s72-c/image_thumb.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-862448278063019439</id><published>2009-10-28T12:01:00.001-07:00</published><updated>2009-10-29T07:04:46.214-07:00</updated><title type='text'>Dynamics GP/SQL Database Login Disconnect</title><content type='html'>&lt;p&gt;This article deals with the following error messages in Dynamics GP:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;“A get/change first operation on table 'coProcess' failed accessing SQL data” &lt;/li&gt;    &lt;li&gt;“[Microsoft][SQL Native Client][SQL Server]The server principal &amp;quot;LESSONUSER2&amp;quot; is not able to access the database &amp;quot;TWO&amp;quot; under the current security context.” &lt;/li&gt;    &lt;li&gt;“The user could not be added to one or more databases.” &lt;/li&gt;    &lt;li&gt;“A get/change first operation on table 'SY_Users_MSTR' failed accessing SQL data” &lt;/li&gt;    &lt;li&gt;“[Microsoft][SQL Native Client][SQL Server]The server principal &amp;quot;LESSONUSER1&amp;quot; is not able to access the database &amp;quot;DYNAMICS&amp;quot; under the current security context.” &lt;/li&gt;    &lt;li&gt;“[Microsoft][SQL Native Client][SQL Server]EXECUTE permission denied on object 'zDP_SY01400SS_1', database 'DYNAMICS', schema 'dbo'.” &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Dynamics GP tracks users separately from SQL Server.&amp;#160; However, there is a one to one relationship between a Dynamics GP user and a SQL Server login.&amp;#160; Both the Dynamics GP user and corresponding SQL Server login have the same username.&amp;#160; Additionally, when a user is granted access to a company, their SQL login is added as a SQL user in that company’s database as part of the DYNGRP role.&amp;#160; The SQL logins associated with Dynamics GP users also have an associated SQL database user in the DYNAMICS database also a member of the DYNGRP role.&lt;/p&gt;  &lt;p&gt;Problems happen when the Dynamics GP users and SQL logins get out sync.&amp;#160; There are five situations when these get out of sync:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The Dynamics GP user has been given access to a company in GP but the corresponding SQL login doesn’t have access to that company. &lt;/li&gt;    &lt;li&gt;The Dynamics GP user doesn’t have access to a company in GP but the corresponding SQL login does have access to the company. &lt;/li&gt;    &lt;li&gt;The Dynamics GP user doesn’t have access to the DYNAMICS database. &lt;/li&gt;    &lt;li&gt;The Dynamics GP user’s corresponding SQL login doesn’t have access to the Dynamics database. &lt;/li&gt;    &lt;li&gt;The Dynamics GP user’s corresponding SQL login isn’t a member of the DYNGRP role in the databases it is supposed to have access to. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;First we’ll look at each of these situations individually and how to individually resolve them.&amp;#160; Then we’ll look at a script that will clean up all of them at once.&lt;/p&gt;  &lt;h5&gt;Dynamics User w/ Missing SQL User&lt;/h5&gt;  &lt;p&gt;This is the case where a Dynamics GP user has been given access to a company in GP but the corresponding SQL login doesn’t have access to that company.&amp;#160; In this case, the user will receive the following error message when logging into Dynamics GP:&lt;/p&gt;  &lt;blockquote&gt;&lt;a href="http://lh6.ggpht.com/_M9HO4tQKytY/SuiU5BG5zuI/AAAAAAAAAC4/ZY7lt3tZcnI/s1600-h/image%5B5%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="261" alt="image" src="http://lh6.ggpht.com/_M9HO4tQKytY/SuiU5R7QLDI/AAAAAAAAAC8/zWwc0ORyeww/image_thumb%5B1%5D.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;Clicking on “More Info” returns the following:&lt;/p&gt;    &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_M9HO4tQKytY/SuiU5vGal5I/AAAAAAAAADA/1k5G7vFbqz4/s1600-h/image%5B2%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="260" alt="image" src="http://lh6.ggpht.com/_M9HO4tQKytY/SuiU6Ohp89I/AAAAAAAAADE/p8uRA0JyErY/image_thumb.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;“[Microsoft][SQL Native Client][SQL Server]The server principal &amp;quot;LESSONUSER2&amp;quot; is not able to access the database &amp;quot;TWO&amp;quot; under the current security context.”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;To resolve this error, go into SQL Management Studio and add the corresponding SQL login to the company database as a member of the DYNGRP role.&lt;/p&gt;  &lt;h4&gt;&lt;/h4&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;h5&gt;SQL User in Company Database w/o Corresponding Dynamics GP User Access&lt;/h5&gt;  &lt;p&gt;In this case, the Dynamics GP user does not have access to a particular company but the corresponding SQL login does have access.&amp;#160; This causes an error in the User Access form if you attempt to add the user to the offending company as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_M9HO4tQKytY/SuiU6QutaeI/AAAAAAAAADI/cpG8KQWGxto/s1600-h/image%5B8%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="234" alt="image" src="http://lh4.ggpht.com/_M9HO4tQKytY/SuiU6vLdhfI/AAAAAAAAADM/0n--PfaDEv4/image_thumb%5B2%5D.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;“The user could not be added to one or more databases.”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;In this case, going into SQL Management Studio and removing the SQL login’s access to the company database will resolve the error and allow the Dynamics GP user to be given access to the company.&lt;/p&gt;  &lt;h5&gt;SQL User Doesn’t Have Access to the DYNAMICS Database&lt;/h5&gt;  &lt;p&gt;In this case, the Dynamics GP user has access to the company database, but doesn’t have access to the DYNAMICS database.&amp;#160; If this happens, the user will see the following error when attempting to login to Dynamics GP.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_M9HO4tQKytY/SuiYlyLfRXI/AAAAAAAAADY/y9ESAaOFULE/s1600-h/image%5B14%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="260" alt="image" src="http://lh6.ggpht.com/_M9HO4tQKytY/SuiYmf_4SNI/AAAAAAAAADc/e0j0OVafFTU/image_thumb%5B4%5D.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;Clicking on “More Info” returns the following:&lt;/p&gt;    &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_M9HO4tQKytY/SuiYmnDM41I/AAAAAAAAADg/USgb9Wdrmeg/s1600-h/image%5B17%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="260" alt="image" src="http://lh5.ggpht.com/_M9HO4tQKytY/SuiYm0BJeuI/AAAAAAAAADk/v4fgpH6QYOo/image_thumb%5B5%5D.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;“[Microsoft][SQL Native Client][SQL Server]The server principal &amp;quot;LESSONUSER1&amp;quot; is not able to access the database &amp;quot;DYNAMICS&amp;quot; under the current security context.”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;To resolve this error, go into SQL Management Studio and add the corresponding SQL login to the DYNAMICS database as a member of the DYNGRP role.&lt;/p&gt;  &lt;h5&gt;SQL User in Company Database w/o Membership in DYNGRP SQL Database Role&lt;/h5&gt;  &lt;p&gt;In this case, the Dynamics GP user has access to a company and their corresponding SQL login has access to the company database, however, they are not a member of the DYNGRP role in the company database.&amp;#160; In this case the user will see the following error when trying to login to the company in question:&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;blockquote&gt;&lt;a href="http://lh6.ggpht.com/_M9HO4tQKytY/SuiU5BG5zuI/AAAAAAAAAC4/ZY7lt3tZcnI/s1600-h/image%5B5%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="261" alt="image" src="http://lh6.ggpht.com/_M9HO4tQKytY/SuiU5R7QLDI/AAAAAAAAAC8/zWwc0ORyeww/image_thumb%5B1%5D.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;Clicking on “More Info” returns the following:&lt;/p&gt;    &lt;p&gt;&lt;a href="http://lh6.ggpht.com/_M9HO4tQKytY/SuiU6oufsgI/AAAAAAAAADQ/WSWcMntHyjU/s1600-h/image%5B11%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="260" alt="image" src="http://lh5.ggpht.com/_M9HO4tQKytY/SuiU7CecVII/AAAAAAAAADU/0zdjVqkGUgw/image_thumb%5B3%5D.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;“[Microsoft][SQL Native Client][SQL Server]EXECUTE permission denied on object 'zDP_SY01300F_1', database 'TWO', schema 'dbo'.”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;To resolve this, go into SQL Management Studio and add the corresponding SQL login to the DYNGRP in the company database in question.&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;h5&gt;SQL User Is Not a Member of the DYNGRP Role in the DYNAMICS Database.&lt;/h5&gt;  &lt;p&gt;In this case, the SQL user is properly setup in the company database and has access to the DYNAMICS database, but is not a member of the DYNGRP role in the DYNAMICS database.&amp;#160; When logging in in this situation, the user will receive the following error: &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_M9HO4tQKytY/SuiYlyLfRXI/AAAAAAAAADY/y9ESAaOFULE/s1600-h/image%5B14%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="260" alt="image" src="http://lh6.ggpht.com/_M9HO4tQKytY/SuiYmf_4SNI/AAAAAAAAADc/e0j0OVafFTU/image_thumb%5B4%5D.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;Clicking on “More Info” returns the following:&lt;/p&gt;    &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_M9HO4tQKytY/SuiYnF6im3I/AAAAAAAAADo/z0uYwK2Q9pU/s1600-h/image%5B20%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="258" alt="image" src="http://lh4.ggpht.com/_M9HO4tQKytY/SuiYnQJiQWI/AAAAAAAAADs/_Vlv4lYlkwg/image_thumb%5B6%5D.png?imgmax=800" width="404" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;“[Microsoft][SQL Native Client][SQL Server]EXECUTE permission denied on object 'zDP_SY01400SS_1', database 'DYNAMICS', schema 'dbo'.”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;To resolve this, go into SQL Management Studio and add the corresponding SQL login to the DYNGRP in the DYNAMICS database.&lt;/p&gt;  &lt;h4&gt;&lt;/h4&gt;  &lt;h4&gt;&lt;/h4&gt;  &lt;p&gt;&lt;/p&gt;  &lt;h5&gt;A Script to Fix it All&lt;/h5&gt;  &lt;p&gt;So the individual fixes are all well and good, but we recently ran a Reformatter project, and in the process, moving from one SQL server to another, at a client with 50+ users and 230+ companies.&amp;#160; In addition, we were reconfiguring all of their security to take advantage of GP 10’s role based security.&amp;#160; Something we hadn’t done during the original upgrade.&lt;/p&gt;  &lt;p&gt;When all was said and done, we ended up with a number of situations where there were various disconnects in the users, mostly SQL logins missing from companies when they should have been there and in companies they shouldn’t.&amp;#160; Fixing all this by hand would have been a nightmare, so we decided to write a script that would fix it.&amp;#160; In this case we had to write a script to write a script (&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/05/run-script-against-multiple-companies.html" target="_blank"&gt;see prior article related to this and how to setup SQL Management Studio to do this&lt;/a&gt;).&amp;#160; Here, we not only had to write a script that created a script against each company, but also against each user as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;--&amp;#160; Don't forget to set Results to Text in your      &lt;br /&gt;--&amp;#160; SQL Management Studio query window.       &lt;br /&gt;--       &lt;br /&gt;--&amp;#160; Set NOCOUNT on so that row count doesn't show       &lt;br /&gt;--&amp;#160; up in the text results.       &lt;br /&gt;SET NOCOUNT ON; &lt;/p&gt;    &lt;p&gt;--&amp;#160; Common table expression that returns every      &lt;br /&gt;--&amp;#160; possible combination of user and company.       &lt;br /&gt;WITH UserCompanyCombinations       &lt;br /&gt;AS (       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SY01400.USERID       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,SY01500.CMPANYID       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,SY01500.INTERID       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM SY01400 -- User Master table       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CROSS JOIN SY01500 -- Company Master table       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE SY01400.USERID NOT IN ('sa', 'DYNSA')       &lt;br /&gt;)       &lt;br /&gt;--&amp;#160; Script to add all users to DYNAMICS database       &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SQLStatement =       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; REPLACE(       &lt;br /&gt;'       &lt;br /&gt;&lt;font color="#ff0000"&gt;USE DYNAMICS        &lt;br /&gt;GO &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;PRINT ''ADDING USER {USERNAME} to DYNAMICS database.'' &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;CREATE USER [{USERNAME}] FROM LOGIN [{USERNAME}]        &lt;br /&gt;GO &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;EXEC sp_addrolemember ''DYNGRP'', ''{USERNAME}''        &lt;br /&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;----------&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;',      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '{USERNAME}', USERID)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM SY01400 -- User Master table       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WHERE USERID NOT IN ('sa', 'DYNSA') &lt;/p&gt;    &lt;p&gt;UNION ALL &lt;/p&gt;    &lt;p&gt;--&amp;#160; Script to remove users from company databases they shouldn't      &lt;br /&gt;--&amp;#160; have access to. &lt;/p&gt;    &lt;p&gt;SELECT&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SQLStatement =       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; REPLACE(REPLACE(       &lt;br /&gt;'       &lt;br /&gt;&lt;font color="#ff0000"&gt;USE {COMPANYDB}        &lt;br /&gt;GO &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;PRINT ''REMOVING USER {USERNAME} from {COMPANYDB} database.'' &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;DROP USER [{USERNAME}]        &lt;br /&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;----------&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;&lt;/font&gt;',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '{COMPANYDB}', UserCompanyCombinations.INTERID)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; , '{USERNAME}', UserCompanyCombinations.USERID)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM UserCompanyCombinations       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; LEFT JOIN SY60100 -- User Access table       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON UserCompanyCombinations.USERID = SY60100.USERID       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND UserCompanyCombinations.CMPANYID = SY60100.CMPANYID       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WHERE SY60100.CMPANYID IS NULL &lt;/p&gt;    &lt;p&gt;UNION ALL &lt;/p&gt;    &lt;p&gt;--&amp;#160; Script to add users to company databases they should have access to      &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SQLStatement =       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; REPLACE(REPLACE(       &lt;br /&gt;'       &lt;br /&gt;&lt;font color="#ff0000"&gt;USE {COMPANYDB}        &lt;br /&gt;GO &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;PRINT ''ADDING USER {USERNAME} to database {COMPANYDB}'' &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;CREATE USER [{USERNAME}] FROM LOGIN [{USERNAME}]        &lt;br /&gt;GO &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;EXEC sp_addrolemember ''DYNGRP'', ''{USERNAME}''        &lt;br /&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;----------&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;&lt;/font&gt;',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '{COMPANYDB}', SY01500.INTERID)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; , '{USERNAME}', SY60100.USERID)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM SY01500 -- Company Master table       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; INNER JOIN SY60100 -- User Access table       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON SY01500.CMPANYID = SY60100.CMPANYID       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WHERE SY60100.USERID NOT IN ('sa', 'DYNSA')&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;As you can see this is a union of three separate queries as follows:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The first query creates a SQL statement for each user, adds them to the DYNAMICS database and then makes them a member of the DYNGRP role.&amp;#160; &lt;/li&gt;    &lt;li&gt;The second query creates a SQL statement for each user and company that the Dynamics GP user doesn’t have access to a company as defined by the SY60100 user access table and removes their SQL user from the associated company database. &lt;/li&gt;    &lt;li&gt;The final query creates a SQL statement for each user and company database that the Dynamics GP users do have access to as defined by the SY60100 user access table, adding the corresponding SQL login to the database as a member of the DYNGRP role. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;You’ll note that in all of the queries we’re excluding the “sa” and “DYNSA” users as these are treated as special users in Dynamics GP.&amp;#160; “sa” should always be in the “sysadmin” server role, giving it full access to all databases.&amp;#160; The “DYNSA” user maps to the “dbo” (database owner) user in each database rather than a user of the same name.&amp;#160; This gives the “DYNSA” user special privileges in the database, such as the ability to modify database objects.&lt;/p&gt;  &lt;p&gt;When you run the script above you should receive something like the following in the results pane:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;USE DYNAMICS      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;PRINT 'ADDING USER LESSONUSER1 to DYNAMICS database.' &lt;/p&gt;    &lt;p&gt;CREATE USER [LESSONUSER1] FROM LOGIN [LESSONUSER1]      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;EXEC sp_addrolemember 'DYNGRP', 'LESSONUSER1'      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;---------- &lt;/p&gt;    &lt;p&gt;USE DYNAMICS      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;PRINT 'ADDING USER LESSONUSER2 to DYNAMICS database.' &lt;/p&gt;    &lt;p&gt;CREATE USER [LESSONUSER2] FROM LOGIN [LESSONUSER2]      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;EXEC sp_addrolemember 'DYNGRP', 'LESSONUSER2'      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;---------- &lt;/p&gt;    &lt;p&gt;USE FOUR      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;PRINT 'REMOVING USER LESSONUSER2 from FOUR database.' &lt;/p&gt;    &lt;p&gt;DROP USER [LESSONUSER2]      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;---------- &lt;/p&gt;    &lt;p&gt;USE TWO      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;PRINT 'ADDING USER LESSONUSER1 to database TWO' &lt;/p&gt;    &lt;p&gt;CREATE USER [LESSONUSER1] FROM LOGIN [LESSONUSER1]      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;EXEC sp_addrolemember 'DYNGRP', 'LESSONUSER1'      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;---------- &lt;/p&gt;    &lt;p&gt;USE TWO      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;PRINT 'ADDING USER LESSONUSER2 to database TWO' &lt;/p&gt;    &lt;p&gt;CREATE USER [LESSONUSER2] FROM LOGIN [LESSONUSER2]      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;EXEC sp_addrolemember 'DYNGRP', 'LESSONUSER2'      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;---------- &lt;/p&gt;    &lt;p&gt;USE FOUR      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;PRINT 'ADDING USER LESSONUSER1 to database FOUR' &lt;/p&gt;    &lt;p&gt;CREATE USER [LESSONUSER1] FROM LOGIN [LESSONUSER1]      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;EXEC sp_addrolemember 'DYNGRP', 'LESSONUSER1'      &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;---------- &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Note that this is a little quick and dirty and running this script will cause errors when it tries to create users that already exist or drop users that don’t.&amp;#160; Adding some extra code to check for a user’s existence or not wouldn’t be that difficult.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-862448278063019439?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/862448278063019439/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/10/dynamics-gpsql-database-user-disconnect.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/862448278063019439'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/862448278063019439'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/10/dynamics-gpsql-database-user-disconnect.html' title='Dynamics GP/SQL Database Login Disconnect'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_M9HO4tQKytY/SuiU5R7QLDI/AAAAAAAAAC8/zWwc0ORyeww/s72-c/image_thumb%5B1%5D.png?imgmax=800' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-5114876894737645762</id><published>2009-09-28T14:08:00.001-07:00</published><updated>2009-09-28T14:08:31.514-07:00</updated><title type='text'>Just Getting It Done</title><content type='html'>&lt;p&gt;Here’s a link to a &lt;a href="http://www.joelonsoftware.com/items/2009/09/23.html" target="_blank"&gt;great blog entry&lt;/a&gt; by Joel Spolsky (&lt;a href="http://www.joelonsoftware.com/index.html" target="_blank"&gt;Joel on Software&lt;/a&gt;) about programmers who just get the job done and delivered.&amp;#160; I’m a great believer in “good enough”.&amp;#160; Not perfectly designed, n-tier architecture, reusable web services, but maintainable code, works, gets the job done and gets delivered.&lt;/p&gt;  &lt;p&gt;Enough said.&amp;#160; Read the article.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-5114876894737645762?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/5114876894737645762/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/09/just-getting-it-done.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/5114876894737645762'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/5114876894737645762'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/09/just-getting-it-done.html' title='Just Getting It Done'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-378287702487792670</id><published>2009-09-25T08:37:00.001-07:00</published><updated>2009-09-25T08:37:54.043-07:00</updated><title type='text'>Stuck Open Payables Voucher</title><content type='html'>&lt;p&gt;We recently had a client call with two payables voucher that were stuck open.&amp;#160; The amount remaining was $0 but there were no payments associated with it.&amp;#160; Because the amount remaining was $0, they were unable to apply any further payments.&amp;#160; In addition, running Check Links and Reconcile didn’t resolve the situation.&lt;/p&gt;  &lt;p&gt;Time to open up Query Analyzer (yes they’re still on SQL 2000).&amp;#160; After some digging, we found that there were Apply records in the PM10200 (apply work table) and the PM20100 (apply open table) for the full amount of the transaction.&amp;#160; Additionally, the date invoice paid off (DINVPDOF) was set and the current transaction amount (CURTRXAM) was set to $0.&lt;/p&gt;  &lt;p&gt;The solution was to remove the apply records, clear out the date invoice paid off and set the current transaction amount to the document amount as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;--Delete orphanned apply to records from&amp;#160; the     &lt;br /&gt;--work table.      &lt;br /&gt;DELETE FROM PM10200      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE VENDORID = '&amp;lt;&amp;lt;MyVendorID&amp;gt;&amp;gt;'      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND APTVCHNM IN ('&amp;lt;&amp;lt;MyVoucherNumber1&amp;gt;&amp;gt;', '&amp;lt;&amp;lt;MyVoucherNumber2&amp;gt;&amp;gt;') &lt;/p&gt;    &lt;p&gt;DELETE FROM PM20100     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE VENDORID = '&amp;lt;&amp;lt;MyVendorID&amp;gt;&amp;gt;'      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND APTVCHNM IN ('&amp;lt;&amp;lt;MyVoucherNumber1&amp;gt;&amp;gt;', '&amp;lt;&amp;lt;MyVoucherNumber2&amp;gt;&amp;gt;') &lt;/p&gt;    &lt;p&gt;--Update the PM transaction header to set the     &lt;br /&gt;--current amount due (CURTRXAM) to the full      &lt;br /&gt;--document amount and the date invoice paid off      &lt;br /&gt;--(DINVPDOF) to empty (1/1/1900)      &lt;br /&gt;UPDATE PM20000 SET      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CURTRXAM = DOCAMNT      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,DINVPDOF = '1/1/1900'      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WHERE VENDORID = '&amp;lt;&amp;lt;MyVendorID&amp;gt;&amp;gt;'      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND VCHRNMBR IN ('&amp;lt;&amp;lt;MyVoucherNumber1&amp;gt;&amp;gt;', '&amp;lt;&amp;lt;MyVoucherNumber2&amp;gt;&amp;gt;')&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;For good measure, we ran Check Links against the Payables Transaction Logical File after completing this.&amp;#160; It didn’t register any errors with the specific vouchers.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-378287702487792670?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/378287702487792670/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/09/stuck-open-payables-voucher.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/378287702487792670'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/378287702487792670'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/09/stuck-open-payables-voucher.html' title='Stuck Open Payables Voucher'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-7883536262191516132</id><published>2009-08-27T12:45:00.001-07:00</published><updated>2009-08-27T12:45:51.308-07:00</updated><title type='text'>Dynamics GP Security Console Error - “Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))”</title><content type='html'>&lt;p&gt;We recently had to add a user to an app that uses the Dynamics GP web services.&amp;#160; Since the web services seem to only allow you to assign permissions to individual users and not Active Directory groups, I was required to go into the Dynamics GP Security Console.&amp;#160; Sadly this often turns into an adventure in error messages and this time was no different.&lt;/p&gt;  &lt;p&gt;First the console errored out at the top level.&amp;#160; A little digging and I discovered that of the three app pools setup by the GP Web Services in IIS, GPWebServicesAppPool, DynamicsSecurityServiceAppPool and DynamicsSecurityAdminServiceAppPool, the first two were using the service account we’d originally setup for this.&amp;#160; The DynamicsSecurityAdminServiceAppPool, however, was using an administrator end user’s account.&amp;#160; Not exactly best practice.&lt;/p&gt;  &lt;p&gt;Anyway, figured that changing it to the same account as the other two app pools would do the trick.&amp;#160; And so it seemed.&amp;#160; I was able to open up the Security Console and navigate down to the policies node, usually the mark of a functioning console.&amp;#160; &lt;/p&gt;  &lt;p&gt;However, when I went to add the new user to the appropriate application level group, I received an error message when I clicked Apply, “An unexpected error has occurred.&amp;#160; See the event log for further details.”&lt;/p&gt;  &lt;p&gt;So into the event log I dove and found the following error:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Pretty much left me stuck.&amp;#160; A couple of emails back and forth with MS support and it turns out that during the Web Services installation, another account is created, DynamicsGPAdm.&amp;#160; This is a local account and is apparently given additional privileges beyond those of a local administrator.&amp;#160; I’m guessing that these are to ADAM.&amp;#160; &lt;/p&gt;  &lt;p&gt;Of course, I had no idea what the password was as this was setup during the install, but fortunately, this account isn’t used for anything else.&amp;#160; So a quick password change and an update to the app pool and I was back on my way.&lt;/p&gt;  &lt;p&gt;However, the complexity (do we really need three separate app pools?) and fragility of the GP Web Services, is one of the main reasons I’ve moved away from using them have moved creating my own web services using eConnect directly.&amp;#160; ADAM may be a great idea in theory, but it has caused me no end of grief.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-7883536262191516132?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/7883536262191516132/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/08/dynamics-gp-security-console-error.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7883536262191516132'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7883536262191516132'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/08/dynamics-gp-security-console-error.html' title='Dynamics GP Security Console Error - “Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))”'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-7109429267899350634</id><published>2009-07-27T14:06:00.001-07:00</published><updated>2009-07-27T19:05:51.626-07:00</updated><title type='text'>“Unable to load client print control” Error When Trying to Print from SQL Reporting Services</title><content type='html'>&lt;p&gt;We’ve seen this error on and off for quite some time but for some reason we’ve had a batch of calls recently regarding it.&amp;#160; &lt;/p&gt;  &lt;p&gt;The error occurs when a user attempts to print a report from SQL Server Reporting Services (SSRS) Report Manager using the printer button on the report viewer.&amp;#160; The issue is caused by an update to Windows Server (ActiveX Kill Bits security update) that breaks the printing function in SSRS.&lt;/p&gt;  &lt;p&gt;You can read about the update in &lt;a title="http://support.microsoft.com/kb/956391" href="http://support.microsoft.com/kb/956391"&gt;http://support.microsoft.com/kb/956391&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;The easiest fix is to apply SQL Server Service Pack 3.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-7109429267899350634?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/7109429267899350634/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/unable-to-load-client-print-control.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7109429267899350634'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7109429267899350634'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/unable-to-load-client-print-control.html' title='“Unable to load client print control” Error When Trying to Print from SQL Reporting Services'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-4821709926181186357</id><published>2009-07-24T18:44:00.001-07:00</published><updated>2009-07-24T18:44:14.446-07:00</updated><title type='text'>“A fatal error was encountered” When Applying Service Pack 3 to Dynamics GP 9.0 on Windows Server 2008</title><content type='html'>&lt;p&gt;First things first.&amp;#160; I would like to give credit to one of our technical consultants, &lt;a href="http://www.linkedin.com/pub/henry-pollock/4/456/4b8" target="_blank"&gt;Henry Pollock&lt;/a&gt;, for getting to the bottom of this one.&lt;/p&gt;  &lt;p&gt;During a recent upgrade, we received the following error when attempting to apply Service Pack 3 on a Dynamics GP 9.0 install on a new Windows Server 2008 box.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_M9HO4tQKytY/SmpjbIbAm8I/AAAAAAAAACA/CDOmYwwmsZk/s1600-h/clip_image002%5B4%5D.jpg"&gt;&lt;img title="clip_image002" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" height="306" alt="clip_image002" src="http://lh3.ggpht.com/_M9HO4tQKytY/SmpjbRHbrrI/AAAAAAAAACE/27xfiv24ifY/clip_image002_thumb%5B1%5D.jpg?imgmax=800" width="497" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Note that the original install of GP 9.0 had gone fine.&amp;#160; A little digging and it turns out you need to disable UAC (User Access Control) on Windows Server 2008 prior to running the service pack install.&lt;/p&gt;  &lt;p&gt;If I were a betting man, I’d guess we’d come across a similar issue on Vista workstations.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-4821709926181186357?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/4821709926181186357/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/fatal-error-was-encountered-when.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4821709926181186357'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4821709926181186357'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/fatal-error-was-encountered-when.html' title='“A fatal error was encountered” When Applying Service Pack 3 to Dynamics GP 9.0 on Windows Server 2008'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_M9HO4tQKytY/SmpjbRHbrrI/AAAAAAAAACE/27xfiv24ifY/s72-c/clip_image002_thumb%5B1%5D.jpg?imgmax=800' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-7828937416652448555</id><published>2009-07-20T16:50:00.001-07:00</published><updated>2009-07-20T17:03:00.891-07:00</updated><title type='text'>Calculating Aggregates in Dynamics GP Integration Manager</title><content type='html'>&lt;p&gt;We recently came across a bit of a kink when developing an accounts payable integration using Integration Manager for a client.&amp;#160; The client was providing us with a single CSV file of expense report line items.&amp;#160; Each line item represented a distribution for an expense report that would contain one or more distributions.&lt;/p&gt;  &lt;p&gt;No problems so far.&amp;#160; We created two sources from the same file, header and distributions.&amp;#160; The header source was grouped by Expense Report ID.&amp;#160; With that we were all set except that it turned out the client was unable to supply us with the expense report total in the source file.&amp;#160; Since Integration Manager doesn’t have any facility to aggregate values when grouping we were stuck.&amp;#160; We had no way to set the Purchases amount in the AP document header.&lt;/p&gt;  &lt;p&gt;We briefly considered preprocessing the source file to add the required column but decided to explore some of the scripting capabilities we hadn’t used in the past.&amp;#160; 20 minutes and 5 lines of VBScript later we’d solved our problem.&lt;/p&gt;  &lt;p&gt;The solution is to keep a running total of the distribution amounts as you’re integrating them and then assign that to the AP Purchases amount before saving each document.&amp;#160; To start with we need a global variable to store our running total and need to initialize it to $0.00.&amp;#160; To do this we add the following VBScript to the Before Integration event of the integration:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SetVariable &amp;quot;PurchaseAmount&amp;quot;, CCur(0.00)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The SetVariable statement saves a value that can be accessed from any other script in the integration.&amp;#160; Note that we’ve explicitly converted it to a currency amount using CCur(0.00).&amp;#160; Global variables are Variants and will take on the type of their initial value.&lt;/p&gt;  &lt;p&gt;Now that we’ve initialized our variable we need to set the running total by capturing the distribution amounts as we process them.&amp;#160; We do this by setting the Debit Amount of the distribution to Use Script and setting the following script”&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SetVariable &amp;quot;PurchaseAmount&amp;quot;, _     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; GetVariable(&amp;quot;PurchaseAmount&amp;quot;) + _      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SourceFields(&amp;quot;Distributions.ExpenseAmount&amp;quot;) &lt;/p&gt;    &lt;p&gt;CurrentField = SourceFields(&amp;quot;Distributions.ExpenseAmount&amp;quot;)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The first line is adding the expense amount to the running total.&amp;#160; The GetVariable function is returning the value of the global variable we set using SetVariable.&amp;#160; The second line is setting the Debit Amount to the current expense amount.&amp;#160; Obviously this is a simple example where we’re assuming that the expense amounts are always positive.&lt;/p&gt;  &lt;p&gt;Once we’ve got our total for the document we want to assign it to the document Purchases amount.&amp;#160; To do this we go back to our integration level events and assign the following script to the Before Document Commit event that takes place right before saving each AP document:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;DestinationFields(&amp;quot;Purchases&amp;quot;) = _     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; GetVariable(&amp;quot;PurchaseAmount&amp;quot;)      &lt;br /&gt;SetVariable &amp;quot;PurchaseAmount&amp;quot;, CCur(0.00)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The first line sets the current documents Purchases amount to the running total.&amp;#160; Since we’re not in working within the destination mappings, we set the Purchases field using the DestinationFields object that gives us access to all of the destination fields.&amp;#160; The second line reinitializes the running total to $0.00 for the next document.&lt;/p&gt;  &lt;p&gt;So there you have it.&amp;#160; The ability to calculate aggregates and running totals within Integration Manager.&amp;#160; Beyond the obvious AP and AR integrations, I’m sure there are numerous useful and probably less the useful purpose for this.&lt;/p&gt;  &lt;p&gt;Aggregate away!&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-7828937416652448555?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/7828937416652448555/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/calculating-aggregates-in-dynamics-gp.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7828937416652448555'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/7828937416652448555'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/calculating-aggregates-in-dynamics-gp.html' title='Calculating Aggregates in Dynamics GP Integration Manager'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-8493445458447514788</id><published>2009-07-17T10:42:00.001-07:00</published><updated>2009-07-17T11:19:06.700-07:00</updated><title type='text'>Automating the Transfer of Dynamics GP Databases – Part 4</title><content type='html'>&lt;p&gt;In parts 1 through 3 we:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp.html"&gt;Created a script to backup all of the Dynamics GP databases.&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_16.html"&gt;Created a script to restore all of the databases.&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_8019.html"&gt;Created a script to transfer all of the related SQL logins.&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;In Part 4 we’re going to tie it altogether.&amp;#160; And to do that we’re going old school.&amp;#160; The batch file.&amp;#160; The following batch file will perform the entire process.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;ECHO OFF &lt;/p&gt;    &lt;p&gt;ECHO Deleting previous backup files from both source and destination folders      &lt;br /&gt;DEL &amp;quot;E:\SQLBackups\*.bak&amp;quot;       &lt;br /&gt;DEL &amp;quot;E:\SQLBackups2\*.bak&amp;quot; &lt;/p&gt;    &lt;p&gt;ECHO Creating backup script      &lt;br /&gt;&amp;quot;C:\Program Files\Microsoft SQL Server\90\Tools\Binn\SQLCMD.EXE&amp;quot; -S GP10DEVPLAYPEN -E -i CreateBackupScript.sql -b -o BackupScript.sql &lt;/p&gt;    &lt;p&gt;ECHO Creating restore script      &lt;br /&gt;&amp;quot;C:\Program Files\Microsoft SQL Server\90\Tools\Binn\SQLCMD.EXE&amp;quot; -S GP10DEVPLAYPEN -E -i CreateRestoreScript.sql -o RestoreScript.sql &lt;/p&gt;    &lt;p&gt;ECHO Creating login transfer script - Using the -d parameter to ensure connecting to the master db      &lt;br /&gt;&amp;quot;C:\Program Files\Microsoft SQL Server\90\Tools\Binn\SQLCMD.EXE&amp;quot; -S GP10DEVPLAYPEN -E -d master -i CreateLoginTransferScript.sql -o LoginTransferScript.sql &lt;/p&gt;    &lt;p&gt;ECHO Backing up databases      &lt;br /&gt;&amp;quot;C:\Program Files\Microsoft SQL Server\90\Tools\Binn\SQLCMD.EXE&amp;quot; -S GP10DEVPLAYPEN -E -i BackupScript.sql -b -o Backup.log &lt;/p&gt;    &lt;p&gt;ECHO Copy back files to folder accessible to second SQL server or instance.      &lt;br /&gt;XCOPY &amp;quot;E:\SQLBackups\*.bak&amp;quot; &amp;quot;E:\SQLBackups2\&amp;quot; &lt;/p&gt;    &lt;p&gt;ECHO Stop and start second SQL instance to ensure that all connections have been cleared out      &lt;br /&gt;net stop mssql$SQL2005_2       &lt;br /&gt;net start mssql$SQL2005_2 &lt;/p&gt;    &lt;p&gt;ECHO Restoring databases      &lt;br /&gt;&amp;quot;C:\Program Files\Microsoft SQL Server\90\Tools\Binn\SQLCMD.EXE&amp;quot; -S GP10DEVPLAYPEN\SQL2005_2 -E -i RestoreScript.sql -b -o Restore.log &lt;/p&gt;    &lt;p&gt;ECHO Transferring logins      &lt;br /&gt;&amp;quot;C:\Program Files\Microsoft SQL Server\90\Tools\Binn\SQLCMD.EXE&amp;quot; -S GP10DEVPLAYPEN\SQL2005_2 -E -i LoginTransferScript.sql -b -o LoginTransfer.log &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;It’s a little difficult to read here (better to cut and paste into a text editor) so here’s what it’s doing:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Deleting all backup files in both the source and destination folders.&amp;#160; &lt;em&gt;Don’t use folders that are being used by other backup files as they will be deleted as well.&lt;/em&gt; &lt;/li&gt;    &lt;li&gt;Creating the backup, restore and login transfer scripts.&amp;#160; &lt;em&gt;Note that all of the creation scripts are run against the source server.&lt;/em&gt; &lt;/li&gt;    &lt;li&gt;Running the backup script against the source server. &lt;/li&gt;    &lt;li&gt;Copying all of the backups to the destination server folder.&amp;#160; In a multi-server environment this would presumably be a share on the destination server. &lt;/li&gt;    &lt;li&gt;This procedure will pick up new company databases and SQL logins used by GP but will not remove deleted ones.&lt;/li&gt;    &lt;li&gt;Stopping and starting the destination SQL instance.&amp;#160; This is a quick and dirty way to kill any connections to the databases you’re restoring. &lt;/li&gt;    &lt;li&gt;Restoring the databases to the destination server. &lt;/li&gt;    &lt;li&gt;Running the logins script. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;A few things to take note of:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;I’m performing this entire operation on single server with two instances of SQL 2005.&amp;#160; However, I am performing all of the actions necessary to do this between two machines. &lt;/li&gt;    &lt;li&gt;This was written and tested using SQL 2005.&amp;#160; Some of the constructs, particularly common table expressions and the use of SQLCMD.EXE in the batch file won’t work in SQL 2000 and would have to be modified.&amp;#160; My guess is that most of this would work in SQL 2008.&lt;/li&gt;    &lt;li&gt;I’m assuming integrated logins.&amp;#160; In other words, the logged in user’s AD login or service account that’s running the batch file needs to be a member of the sysadmin role on both SQL server instances. &lt;/li&gt;    &lt;li&gt;I came across an error in the restore when there was an additional but empty data file associated with a database.&amp;#160; It wasn’t included in the backup and so errored on the MOVE clause in the restore for that file. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The trickiest part of this was developing the restore script as I needed to take into account all of the possible scenarios of the database files.&amp;#160; Certainly, having the same volume and folders structure on your destination environment would simplify that.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Are There Better Ways of Doing This?&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;That’s always the question isn’t it.&amp;#160; There are a hundred ways to skin a cat.&amp;#160; Was the 8 hours I spent putting all this together worth it?&amp;#160; I certainly learned a few new things. &lt;/p&gt;  &lt;p&gt;There are certainly other ways of doing it.&amp;#160; Other technologies that would be interesting to explore in this regard include:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;SSIS &lt;/li&gt;    &lt;li&gt;.NET or ASP.NET apps &lt;/li&gt;    &lt;li&gt;PowerShell.&amp;#160; I haven’t had the chance to play with PowerShell but it’s becoming the default command line tool for most Microsoft products. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;for now I’ll leave that exploration to others.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-8493445458447514788?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/8493445458447514788/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_17.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/8493445458447514788'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/8493445458447514788'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_17.html' title='Automating the Transfer of Dynamics GP Databases – Part 4'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-3854713583087275293</id><published>2009-07-16T19:23:00.001-07:00</published><updated>2009-07-17T11:10:02.828-07:00</updated><title type='text'>Automating the Transfer of Dynamics GP Databases – Part 3</title><content type='html'>&lt;p&gt;In &lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp.html"&gt;Parts 1&lt;/a&gt; and &lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_16.html"&gt;2&lt;/a&gt; we created scripts that backup all of the Dynamics GP databases and then restore them to another SQL instance.&amp;#160; Once that’s done we’ll need refresh security.&amp;#160; To do that we need to transfer the SQL logins, except sa, used by Dynamics GP.&lt;/p&gt;  &lt;p&gt;The following SQL script creates a login script that can be run against the second SQL to create the logins, including their original passwords and SIDS.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;--&amp;#160; Ensure that when running this script using SQLCMD.EXE that you set the      &lt;br /&gt;--&amp;#160; database to master.       &lt;br /&gt;--       &lt;br /&gt;--&amp;#160; Setting NOCOUNT on prevents the row count message from appearing at the bottom.       &lt;br /&gt;--&amp;#160; Note that the SQL statements do not include column headers.&amp;#160; This makes       &lt;br /&gt;--&amp;#160; the entire output of the results window executable. &lt;/p&gt;    &lt;p&gt;SET NOCOUNT ON &lt;/p&gt;    &lt;p&gt;IF&amp;#160; EXISTS (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT *       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM sys.objects       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE object_id = OBJECT_ID(N'[dbo].[fn_Hexadecimal]')       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DROP FUNCTION [dbo].[fn_Hexadecimal] &lt;/p&gt;    &lt;p&gt;GO      &lt;br /&gt;SET ANSI_NULLS ON       &lt;br /&gt;GO       &lt;br /&gt;SET QUOTED_IDENTIFIER ON       &lt;br /&gt;GO       &lt;br /&gt;-- =============================================       &lt;br /&gt;-- Author:&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Limey       &lt;br /&gt;-- Create date: 7/16/2009       &lt;br /&gt;-- Description:&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;--&amp;#160;&amp;#160; This function converts a varbinary to a       &lt;br /&gt;--&amp;#160;&amp;#160; hexadecimal string value.       &lt;br /&gt;--&amp;#160;&amp;#160; It is used to convert a password hash and       &lt;br /&gt;--&amp;#160;&amp;#160; login SID to a string representation.       &lt;br /&gt;-- =============================================       &lt;br /&gt;CREATE FUNCTION dbo.fn_Hexadecimal       &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; -- Add the parameters for the function here       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @binvalue varbinary(256)       &lt;br /&gt;)       &lt;br /&gt;RETURNS varchar (514)       &lt;br /&gt;AS       &lt;br /&gt;BEGIN       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @charvalue varchar (514)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @i int       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @length int       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DECLARE @hexstring char(16)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT @charvalue = '0x'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT @i = 1       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT @length = DATALENGTH (@binvalue)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT @hexstring = '0123456789ABCDEF'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WHILE (@i &amp;lt;= @length)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; BEGIN       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; DECLARE @tempint int       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; DECLARE @firstint int       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; DECLARE @secondint int       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT @tempint = CONVERT(int, SUBSTRING(@binvalue,@i,1))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT @firstint = FLOOR(@tempint/16)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT @secondint = @tempint - (@firstint*16)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT @charvalue = @charvalue +       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SUBSTRING(@hexstring, @firstint+1, 1) +       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SUBSTRING(@hexstring, @secondint+1, 1)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT @i = @i + 1       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; END &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; -- Return the result of the function      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; RETURN @charvalue       &lt;br /&gt;END       &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;--&amp;#160; This common table expression returns all enabled Dynamics GP      &lt;br /&gt;--&amp;#160; logins and their properties.       &lt;br /&gt;WITH DynamicsGPLogins       &lt;br /&gt;AS (       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SID = dbo.fn_Hexadecimal(ServerPrincipals.sid)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,UserName = ServerPrincipals.name       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,DefaultDatabaseName = ServerPrincipals.default_database_name       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,IsPolicyChecked =       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN SQLLogins.is_policy_checked&amp;#160; = 1 THEN 'ON'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE 'OFF'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,IsExpirationChecked =       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN SQLLogins.is_expiration_checked&amp;#160; = 1 THEN 'ON'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE 'OFF'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,PasswordHash = dbo.fn_Hexadecimal(CAST(LOGINPROPERTY(ServerPrincipals.name, 'PasswordHash') AS varbinary (256)))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM sys.server_principals AS ServerPrincipals       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; LEFT JOIN sys.syslogins AS Logins       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON Logins.name = ServerPrincipals.name       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; LEFT JOIN sys.sql_logins AS SQLLogins       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON ServerPrincipals.name = SQLLogins.name       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE ServerPrincipals.type IN ( 'S', 'G', 'U' )       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND ISNULL(Logins.hasaccess, 0) = 1       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND ISNULL(Logins.denylogin, 1) = 0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND ISNULL(ServerPrincipals.is_disabled, 1) = 0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND ServerPrincipals.name &amp;lt;&amp;gt; 'sa'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AND ServerPrincipals.name IN (       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; USERID       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM DYNAMICS.dbo.SY01400)       &lt;br /&gt;) &lt;/p&gt;    &lt;p&gt;--&amp;#160; This select statement uses the list of Dynamics GP      &lt;br /&gt;--&amp;#160; logins to create a create login script.       &lt;br /&gt;SELECT       &lt;br /&gt;'       &lt;br /&gt;IF EXISTS (       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT *       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM sys.server_principals       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE name = N''' + UserName + ''')       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DROP LOGIN ' + QUOTENAME(UserName) + '       &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;CREATE LOGIN ' + QUOTENAME(UserName) + '      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH PASSWORD = ' + PasswordHash + ' HASHED,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SID = ' + SID + ',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DEFAULT_DATABASE = ' + DefaultDatabaseName + ',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; CHECK_POLICY = ' + IsPolicyChecked + ',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; CHECK_EXPIRATION = ' + IsExpirationChecked + '       &lt;br /&gt;GO       &lt;br /&gt;'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM DynamicsGPLogins&lt;/p&gt;    &lt;p&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;There are two basic sections to this query.&amp;#160; The first creates a scalar function in the master database that converts a varbinary to a varchar.&amp;#160; This is used to convert the SID and password hash to a text representation that can be put in the CREATE LOGIN statement.&lt;/p&gt;  &lt;p&gt;The second section of the script actually creates the login script.&amp;#160; There are two parts to this are:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The common table expression that returns SQL logins associated with Dynamics GP (from the DYNAMICS SY01400 table) and the associated properties.&amp;#160; It filters out any logins that are disabled or have been denied access. &lt;/li&gt;    &lt;li&gt;The final part is a select that uses this common table expression to actually create the CREATE LOGIN statements. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;This is a simplified version of the SQL login transfer script found in &lt;a href="http://support.microsoft.com/kb/918992/" target="_blank"&gt;Microsoft Knowledgebase Article 918992&lt;/a&gt; and will produce the following SQL script:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- &lt;/p&gt;    &lt;p&gt;IF EXISTS (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT *       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM sys.server_principals       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE name = N'DYNSA')       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DROP LOGIN [DYNSA]       &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;CREATE LOGIN [DYNSA]      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH PASSWORD = 0x01008E7655067D77DE8FCC167C184C22B8B47BC3CA3CA2FFA86E HASHED,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SID = 0x42F411B53282584D96B94C0B09491091,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DEFAULT_DATABASE = master,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; CHECK_POLICY = OFF,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; CHECK_EXPIRATION = OFF       &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;IF EXISTS (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT *       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM sys.server_principals       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE name = N'LESSONUSER1')       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DROP LOGIN [LESSONUSER1]       &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;CREATE LOGIN [LESSONUSER1]      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH PASSWORD = 0x0100F81A338610819AD7298F890F6DF93302BC329D14B0F23D9D HASHED,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SID = 0xA33F190403906349BA4074FC68B7F883,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DEFAULT_DATABASE = master,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; CHECK_POLICY = OFF,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; CHECK_EXPIRATION = OFF       &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;IF EXISTS (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT *       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM sys.server_principals       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE name = N'LESSONUSER2')       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DROP LOGIN [LESSONUSER2]       &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;CREATE LOGIN [LESSONUSER2]      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH PASSWORD = 0x0100C093A6586BBC58FE0CB74886EC4F0AB442FC60766EF9805E HASHED,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SID = 0xBF47CE1CB51C1A4CABC2A75980AAF78D,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DEFAULT_DATABASE = master,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; CHECK_POLICY = OFF,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; CHECK_EXPIRATION = OFF       &lt;br /&gt;GO&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Note that since this creates logins with the same SIDs, they’ll automatically be associated with database users.&lt;/p&gt;  &lt;p&gt;In &lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_17.html"&gt;Part 4&lt;/a&gt; we’ll look at putting this all together.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-3854713583087275293?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/3854713583087275293/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_8019.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/3854713583087275293'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/3854713583087275293'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_8019.html' title='Automating the Transfer of Dynamics GP Databases – Part 3'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-6906385491439894520</id><published>2009-07-16T14:37:00.001-07:00</published><updated>2009-07-17T11:54:27.837-07:00</updated><title type='text'>Automating the Transfer of Dynamics GP Databases – Part 2</title><content type='html'>&lt;p&gt;In &lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp.html"&gt;Part 1&lt;/a&gt; we wrote a SQL script that will create a backup script for all of the Dynamics GP databases to help us automate the transfer all of Dynamics GP’s databases to another SQL server or instance.&amp;#160; In Part 2 we’ll do the same for restoring the databases.&lt;/p&gt;  &lt;p&gt;First, let’s review the T-SQL for restoring a database.&amp;#160; In this case the DYNAMICS database.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;RESTORE DATABASE DYNAMICS      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM DISK = 'E:\SQLBackups2\DYNAMICSTransfer.bak'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH RECOVERY,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE 'GPSDYNAMICSDat.mdf' TO 'E:\SQLData2\GPSDYNAMICSDat.mdf',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE 'GPSDYNAMICSLog.ldf' TO 'E:\SQLData2\GPSDYNAMICSLog.ldf'&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Not too bad but there’s a bit of a kink here.&amp;#160; The MOVE clauses put the the database files in the desired location on the destination instance.&amp;#160; If you're putting the files in the exact same folder structure on the destination as the source then we’re all set and can skip the MOVE clauses and build our RESTORE statements the same way as the backup script as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;--&amp;#160; Setting NOCOUNT on prevents the row count message from appearing at the bottom.      &lt;br /&gt;--&amp;#160; Note that the SQL statements do not include column headers.&amp;#160; This makes       &lt;br /&gt;--&amp;#160; the entire output of the results window executable. &lt;/p&gt;    &lt;p&gt;SET NOCOUNT ON &lt;/p&gt;    &lt;p&gt;DECLARE @BackupFolder nvarchar(max)      &lt;br /&gt;SET @BackupFolder = 'E:\SQLBackups2\' &lt;/p&gt;    &lt;p&gt;--&amp;#160; Create statement to backup DYNAMICS database      &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; RESTORE DATABASE DYNAMICS       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM DISK = ''' + @BackupFolder + 'DYNAMICSTransfer.bak''       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WITH RECOVERY       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GO       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ' &lt;/p&gt;    &lt;p&gt;UNION ALL &lt;/p&gt;    &lt;p&gt;--&amp;#160; Create statements to backup company databases.      &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; REPLACE(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; RESTORE DATABASE {COMPANYDB}       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM DISK = ''' + @BackupFolder + '{COMPANYDB}Transfer.bak''       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WITH RECOVERY       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GO       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '{COMPANYDB}', LTRIM(RTRIM(INTERID)))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM DYNAMICS.dbo.SY01500&lt;/p&gt;    &lt;p&gt;&amp;#160;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;If, as in many cases, your folder structure is different on the machine you’re transferring to, you need to deal with the MOVE clause.&amp;#160; To do this we need to know how many files there are and their logical file names.&amp;#160; &lt;/p&gt;  &lt;p&gt;If we were dealing strictly with Dynamics GP Utilities created databases with the default files we could easily just hardcode the two MOVE clauses and insert the company ID in the appropriate places.&amp;#160; However, if you’ve added files to a database (such as spanning it over multiple volumes), or restored one company database over another (such as restoring a production database over a training one for training purposes), your logical file names will not be standard.&lt;/p&gt;  &lt;p&gt;The database file information is most easily extracted from the master.sys.master_files system view in the master database.&amp;#160; Once we have the list of files we can build a statement that creates a restore script as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;--&amp;#160; Setting NOCOUNT on prevents the row count message from appearing at the bottom.      &lt;br /&gt;--&amp;#160; Note that the SQL statements do not include column headers.&amp;#160; This makes       &lt;br /&gt;--&amp;#160; the entire output of the results window executable. &lt;/p&gt;    &lt;p&gt;SET NOCOUNT ON; &lt;/p&gt;    &lt;p&gt;--Declare and set variables for the restore folders      &lt;br /&gt;DECLARE @BackupFolder nvarchar(max);       &lt;br /&gt;DECLARE @NewDataFolder nvarchar(max);       &lt;br /&gt;DECLARE @NewLogsFolder nvarchar(max); &lt;/p&gt;    &lt;p&gt;SET @BackupFolder = 'E:\SQLBackups2\';      &lt;br /&gt;SET @NewDataFolder = 'E:\SQLData2\';       &lt;br /&gt;SET @NewLogsFolder = 'E:\SQLData2\'; &lt;/p&gt;    &lt;p&gt;--&amp;#160; This common table expression returns the list of Dynamics      &lt;br /&gt;--&amp;#160; databases, identifying the min and max file IDs.       &lt;br /&gt;WITH Databases       &lt;br /&gt;AS       &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; DatabaseID = Databases.database_id       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,DatabaseName = Databases.name       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,MinFileID = MIN(DatabaseFiles.file_id)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,MaxFileID = MAX(DatabaseFiles.file_id)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM master.sys.databases AS Databases       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; INNER JOIN master.sys.master_files AS DatabaseFiles       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON Databases.database_id = DatabaseFiles.database_id       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHERE Databases.name = 'DYNAMICS'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; OR Databases.name IN (       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; INTERID       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM DYNAMICS.dbo.SY01500)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GROUP BY       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Databases.database_id       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,Databases.name       &lt;br /&gt;), &lt;/p&gt;    &lt;p&gt;--&amp;#160; This common table expression returns the list of files associated      &lt;br /&gt;--&amp;#160; with the Dynamics databases, identifying the first and last files       &lt;br /&gt;--&amp;#160; in the list.       &lt;br /&gt;DatabaseFiles       &lt;br /&gt;AS       &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; DatabaseName = RTRIM(LTRIM(Databases.DatabaseName))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,LogicalFileName = DatabaseFiles.name       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,PhysicalFileName =       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN DatabaseFiles.file_id &amp;gt; 2 THEN       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN type_desc = 'ROWS' THEN 'GPS'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + RTRIM(LTRIM(Databases.DatabaseName))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + 'Dat_' + CAST(DatabaseFiles.file_id AS nvarchar(3)) + '.' + SUBSTRING(physical_name, (LEN(physical_name) - 2), 3)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE 'GPS' + RTRIM(LTRIM(Databases.DatabaseName)) + 'Log_'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + CAST(DatabaseFiles.file_id AS nvarchar(3)) + '.' + SUBSTRING(physical_name, (LEN(physical_name) - 2), 3)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN type_desc = 'ROWS' THEN 'GPS'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + RTRIM(LTRIM(Databases.DatabaseName)) + 'Dat.' + SUBSTRING(physical_name, (LEN(physical_name) - 2), 3)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE 'GPS'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + RTRIM(LTRIM(Databases.DatabaseName)) + 'Log.' + SUBSTRING(physical_name, (LEN(physical_name) - 2), 3)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,FileType = type_desc       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,FileID = DatabaseFiles.file_id       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,IsFirstFile =       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN DatabaseFiles.file_id = Databases.MinFileID THEN 1       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE 0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ,IsLastFile =       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN DatabaseFiles.file_id = Databases.MaxFileID THEN 1       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE 0       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FROM Databases       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; INNER JOIN master.sys.master_files AS DatabaseFiles       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ON Databases.DatabaseID = DatabaseFiles.database_id       &lt;br /&gt;) &lt;/p&gt;    &lt;p&gt;--&amp;#160; The SELECT statement outputs the appropriate parts of the      &lt;br /&gt;--&amp;#160; RESTORE statement depending on whether this the first, last       &lt;br /&gt;--&amp;#160; or a middle file.       &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN IsFirstFile = 1 THEN       &lt;br /&gt;'       &lt;br /&gt;RESTORE DATABASE ' + DatabaseName + '       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM DISK = ''' + @BackupFolder + DatabaseName + 'Transfer.bak''       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH RECOVERY,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE ''' + LogicalFileName + ''' TO ''' + @NewDataFolder + PhysicalFileName + ''',       &lt;br /&gt;'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN IsLastFile = 1 THEN       &lt;br /&gt;'&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE ''' + LogicalFileName + ''' TO ''' + @NewDataFolder + PhysicalFileName + '''       &lt;br /&gt;GO       &lt;br /&gt;'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE       &lt;br /&gt;'&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE ''' + LogicalFileName + ''' TO ''' + @NewDataFolder + PhysicalFileName + ''',       &lt;br /&gt;'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM DatabaseFiles       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ORDER BY       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; DatabaseName,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FileID&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;So this is a little involved and is best read by copying into a query window but essentially we’re doing the following:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The first common table expression, Databases, returns a row for each Dynamics database and includes the minimum and maximum file IDs.&amp;#160; This will allow us to know what the first and last files are for a particular database and thus know when to prepend the RESTORE clause at the beginning and the GO statement at the end. &lt;/li&gt;    &lt;li&gt;The second common table expression, DatabaseFiles, returns the list of database files with their logical names and new physical locations.&amp;#160; Additionally, it indicates whether a particular file is the first or last one in the list. &lt;/li&gt;    &lt;li&gt;The final SELECT statement actually builds the RESTORE statement using the DatabaseFiles common table expression. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Using my two company test environment, you get the following restore script:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- &lt;/p&gt;    &lt;p&gt;RESTORE DATABASE DYNAMICS      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM DISK = 'E:\SQLBackups2\DYNAMICSTransfer.bak'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH RECOVERY,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE 'GPSDYNAMICSDat.mdf' TO 'E:\SQLData2\GPSDYNAMICSDat.mdf',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE 'GPSDYNAMICSLog.ldf' TO 'E:\SQLData2\GPSDYNAMICSLog.ldf'       &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;RESTORE DATABASE FOUR      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM DISK = 'E:\SQLBackups2\FOURTransfer.bak'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH RECOVERY,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE 'GPSFOURDat.mdf' TO 'E:\SQLData2\GPSFOURDat.mdf',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE 'GPSFOURLog.ldf' TO 'E:\SQLData2\GPSFOURLog.ldf',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE 'FOURDat_2' TO 'E:\SQLData2\GPSFOURDat_3.ndf'       &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;RESTORE DATABASE TWO      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM DISK = 'E:\SQLBackups2\TWOTransfer.bak'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH RECOVERY,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE 'GPSTWODat.mdf' TO 'E:\SQLData2\GPSTWODat.mdf',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MOVE 'GPSTWOLog.ldf' TO 'E:\SQLData2\GPSTWOLog.ldf'       &lt;br /&gt;GO&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Note that the FOUR company database has had it’s additional data file properly listed in the MOVE clauses.&lt;/p&gt;  &lt;p&gt;If we now run this script against our second server (or server instance), we’ll restore all of the databases we backed up previously.&lt;/p&gt;  &lt;p&gt;In our &lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_8019.html"&gt;Part 3&lt;/a&gt; we’ll look at how to reattach logins to database users.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-6906385491439894520?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/6906385491439894520/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_16.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/6906385491439894520'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/6906385491439894520'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_16.html' title='Automating the Transfer of Dynamics GP Databases – Part 2'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-4444002080181054814</id><published>2009-07-15T18:28:00.001-07:00</published><updated>2009-07-17T11:14:30.418-07:00</updated><title type='text'>Automating the Transfer of Dynamics GP Databases – Part 1</title><content type='html'>&lt;p&gt;Once upon a time I wrote a &lt;a href="http://dynamicsgpgeek.blogspot.com/2009/05/run-script-against-multiple-companies.html"&gt;blog entry&lt;/a&gt; on how to write a script that writes a script that runs against every Dynamics GP company database.&amp;#160; Now let’s do something useful with it.&lt;/p&gt;  &lt;p&gt;More and more of our clients are having to deal with the alphabet soup regulatory compliance such as SOX (Sarbanes Oxley), PCI DSS, HIPPA etc. or simply deciding to implement good IT change control policies.&amp;#160; Often this means that before deploying that fancy new eConnect integration to production, we need to test it in a separate test environment.&lt;/p&gt;  &lt;p&gt;No big deal.&amp;#160; Build out a new server, install SQL, GP, copy over the databases…How many companies did you say you had?&amp;#160; 200…huh…Better cancel my dinner plans.&lt;/p&gt;  &lt;p&gt;Best of all, you really need to keep that environment refreshed on a pretty regular basis for support purposes.&amp;#160; So how do you transfer 200 company databases (and don’t forget they lowly DYNAMICS database) and still manage to make it home for The Daily Show?&lt;/p&gt;  &lt;p&gt;In this four part entry we’ll figure out how to:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Backup all of the Dynamics GP databases.&lt;/li&gt;    &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_16.html"&gt;Restore all of the databases.&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_8019.html"&gt;Transfer all of the logins.&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_17.html"&gt;Pull all of the tasks together.&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Let’s start with backing up.&amp;#160; Time to dig up the dusty Books Online and find out how to backup a database in the query window.&amp;#160; No double clicking here.&amp;#160; Here’s how we’d backup the DYNAMICS database:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;BACKUP DATABASE DYNAMICS      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; TO DISK = 'E:\SQLBackups\DYNAMICSTransfer.bak'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WITH FORMAT,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MEDIANAME = 'DYNAMICSBackup',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; NAME = 'Full Backup of DYNAMICS for Transfer'&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;So, let’s use our handy dandy code to blow that out so that we build a script that backs up the DYNAMICS database and then each of the company databases.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;--&amp;#160; Setting NOCOUNT on prevents the row count message from appearing at the bottom.      &lt;br /&gt;--&amp;#160; Note that the SQL statements do not include column headers.&amp;#160; This makes       &lt;br /&gt;--&amp;#160; the entire output of the results window executable. &lt;/p&gt;    &lt;p&gt;SET NOCOUNT ON &lt;/p&gt;    &lt;p&gt;DECLARE @BackupFolder nvarchar(max)      &lt;br /&gt;SET @BackupFolder = 'E:\SQLBackups\' &lt;/p&gt;    &lt;p&gt;--&amp;#160; Create statement to backup DYNAMICS database      &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BACKUP DATABASE DYNAMICS       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; TO DISK = ''' + @BackupFolder + 'DYNAMICSTransfer.bak''       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WITH FORMAT,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MEDIANAME = ''DYNAMICSBackup'',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; NAME = ''Full Backup of DYNAMICS for Transfer'' &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GO      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ' &lt;/p&gt;    &lt;p&gt;UNION ALL &lt;/p&gt;    &lt;p&gt;--&amp;#160; Create statements to backup company databases.      &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; REPLACE(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BACKUP DATABASE {COMPANYDB}       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; TO DISK = ''' + @BackupFolder + '{COMPANYDB}Transfer.bak''       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WITH FORMAT,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MEDIANAME = ''{COMPANYDB}Backup'',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; NAME = ''Full Backup of {COMPANYDB} for Transfer'' &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GO      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '{COMPANYDB}', LTRIM(RTRIM(INTERID)))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM DYNAMICS.dbo.SY01500&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;This should produce the following backup script that will backup all of the Dynamics databases:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BACKUP DATABASE DYNAMICS      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; TO DISK = 'E:\SQLBackups\DYNAMICSTransfer.bak'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WITH FORMAT,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MEDIANAME = 'DYNAMICSBackup',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; NAME = 'Full Backup of DYNAMICS for Transfer' &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GO&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BACKUP DATABASE FOUR      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; TO DISK = 'E:\SQLBackups\FOURTransfer.bak'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WITH FORMAT,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MEDIANAME = 'FOURBackup',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; NAME = 'Full Backup of FOUR for Transfer' &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GO&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BACKUP DATABASE TWO      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; TO DISK = 'E:\SQLBackups\TWOTransfer.bak'       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WITH FORMAT,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MEDIANAME = 'TWOBackup',       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; NAME = 'Full Backup of TWO for Transfer' &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; GO&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;At this point we’re all set to backup the databases.&amp;#160; In &lt;a href="http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp_16.html"&gt;Part 2&lt;/a&gt; we’ll create a script that restores them.&lt;/p&gt;&lt;/blockquote&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-4444002080181054814?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/4444002080181054814/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4444002080181054814'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4444002080181054814'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/automating-transfer-of-dynamics-gp.html' title='Automating the Transfer of Dynamics GP Databases – Part 1'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-4115129636080869978</id><published>2009-07-15T17:15:00.001-07:00</published><updated>2009-07-15T17:15:01.091-07:00</updated><title type='text'>Dynamics GP, SQL 2008, Windows 2008 and 64bit</title><content type='html'>&lt;p&gt;Wow, it’s been over a month since my last post.&amp;#160; I guess between a week and a bit of unplugged (from work at least) vacation and the resulting crush of undone work afterword got me out of the swing of things.&lt;/p&gt;  &lt;p&gt;Due to the upcoming end of support for GP version 8, we’re in the midst of a lot of version 10 upgrades.&amp;#160; For those clients still on version 8, they’re often on older hardware, OS and SQL versions and so are often moving to new hardware and are looking for OS and SQL Server guidance.&amp;#160; Should they go to SQL 2008?&amp;#160; What are the advantages of 64bit?&amp;#160; &lt;/p&gt;  &lt;p&gt;These questions have come from our clients and functional consultants (the people may not be able to write a recursive common table expression but actually know what deferred revenue is).&amp;#160; Rather than simply continue to answer queries from clients and consultants on a one-off basis I sent the following in an email to our consultants and thought I might share it.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;Why 64bit?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;32bit Windows can only access up to 4GB or RAM.&amp;#160; In addition, 2GB is reserved for the OS with only 2GB remaining for applications.&amp;#160; This means that at&amp;#160; a base level, 32bit SQL Server can access at most, 2GB of RAM.&amp;#160; There are some system gymnastics that will allow you to access up to 3GB on Windows Server Standard or 7GB on Windows Server Enterprise but due to the required gymnastics these are not ideal.&amp;#160; In addition, due to the fact that a 64bit OS processes data in 64bit rather than 32bit chunks, it can process more data faster.&amp;#160; No gymnastics are required.&lt;/p&gt;  &lt;p&gt;The 64bit version of Windows Server Standard(2003 or 2008) allows you to address up to 32GB of physical RAM.&amp;#160; The 64bit version of Windows Server Enterprise allows you to address up to 1TB of physical RAM.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;So how does this help with SQL Server?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;The more of a database that SQL Server can load into memory, the faster its performance will be.&amp;#160; If it can cache an entire database into memory then the only time it has to access the disk is for writes.&amp;#160; All reads are done from memory and thus considerably faster than reading from disk.&amp;#160; In an ideal world you would want to have enough free memory to load all of your commonly used databases.&amp;#160; So if you have 4 3GB databases, you would ideally want to have 12GB or RAM plus the RAM required for the OS and any other applications.&amp;#160; In addition to being able to address this amount, a 64bit SQL Server will have improved performance due to the 64bit data processing itself.&amp;#160; In fact, there should be performance improvements even when running 64bit SQL on a server with only 4GB or RAM.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;How does 64bit SQL Server affect my Dynamics GP implementation?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;64bit SQL Server itself should not present any issues with a Dynamics GP implementation.&amp;#160; GP will see 64bit or 32bit SQL as the same.&amp;#160; However, due to the fact that SQL Server 64bit must be installed on a 64bit OS there are issues with regards to installing the Dynamics GP client on the SQL Server.&amp;#160; Some components, most notably the FRx client are not supported on 64bit Oss, even though they support connecting to a 64bit SQL Server.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;So what about the Dynamics GP client and components?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;In the past, we’ve generally installed a “server” install of the Dynamics GP client on the SQL Server.&amp;#160; This includes the Dyndata file share that is used by the workstation clients.&amp;#160; Unlike earlier versions Dynamics GP, there is no longer a separate “server” install of the client.&amp;#160; All clients are equal and all “server” functions can be performed from any client workstation with little to no adverse affects on performance.&amp;#160; &lt;/p&gt;  &lt;p&gt;As a best practice, it is always advisable to avoid installing additional applications on the SQL Server for the following reasons:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;To maximize the amount of resources&amp;#160; (CPU, RAM and disk) available for SQL itself.&lt;/li&gt;    &lt;li&gt;To reduce or eliminate the need to remote desktop directly into the SQL server for security reasons.&amp;#160; This also reduces the possibility of inadvertent reboots or system changes of a production SQL server.&lt;/li&gt;    &lt;li&gt;To reduce the possibility of additional applications or services interfering with SQL or other services and causing instability issues on the SQL Server.&lt;/li&gt;    &lt;li&gt;Avoiding issues with clustering if the SQL server currently is or is planned to by clustered.&lt;/li&gt;    &lt;li&gt;Avoiding issues with installing the client on a 64bit OS.&lt;/li&gt;    &lt;li&gt;To allow the SQL hardware to be upgraded/replaced without affecting the Dynamics GP install (except for possibly the client ODBC connections)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;So where should I install the Dynamics GP client?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;It is recommended that a “server” or “administrative” copy of the Dynamics GP client be installed on a dedicated application server.&amp;#160; This application server should be a 32bit OS.&amp;#160; This is where the Dyndata folder will reside as well as additional components such as eConnect, Web Services, RDM etc.&amp;#160; In addition, it is recommended that a copy of the client’s current version of Microsoft Office (at least Excel) also be installed on this server for the purposes of troubleshooting GP/Office functionality.&amp;#160; Also, a copy of the SQL Server Client Tools (SQL Server Management Studio and SQL Server Business Intelligence Studio) should be installed on this server so that most, if not all, SQL administrative functions can be performed without remote desktopping into the SQL server.&lt;/p&gt;  &lt;p&gt;As noted, this should ideally be a dedicated server so that the Dynamics GP install and the various components do not adversely affect or are not adversely affected by other applications installed on the server.&amp;#160; Since many of our clients have implemented virtual environments, and this is a perfect candidate for a virtual server (despite Microsoft’s statements to the contrary), it should not be an additional burden to create and support this environment.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;When is it appropriate to recommend a single server environment?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;If the client has a small number of users (&amp;lt;=10) and it is not expected that the total size of all of the active databases (Dynamics or other applications) will exceed 4GB over the life of the installation and the client is not in a virtualized environment and so would require additional hardware and OS licenses for an application server then it would be appropriate to recommend a single server environment.&amp;#160; This environment would be required to be 32bit OS and 32bit SQL Server.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;Is a two server environment the most I’ll need?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;No.&amp;#160; If you have a particularly high transaction environment on your SQL server, such as high transaction Dynamics environment or other large, high transaction databases other than Dynamics GP, then it would be appropriate to split off other components of SQL Server, such as Reporting Services and Analysis Services to their own dedicated servers.&amp;#160;&amp;#160;&amp;#160; Note that in a clustered SQL Environment, Reporting Services and Analysis Services would be required to be put on servers other than the SQL Server.&amp;#160; If this were the case then the Enterprise version of SQL Server would be required. &lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;What about SQL Server 2008?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;Now that Microsoft has released Service Pack 1 for SQL Server 2008, more clients may be looking to deploy it to take advantage of some of its new features.&amp;#160; Most of the commonly used components are Dynamics GP, including SRS reports, are supported on SQL Server 2008 when using SP2 or SP3 of Dynamics GP or the particular GP component.&amp;#160; FRx is currently not supported on SQL Server 2008, however, it has been installed and is working correctly on SQL Server 2008 in the Caturano Dynamics GP environment.&amp;#160; Microsoft is currently evaluating FRx’s compatibility with SQL Server 2008 and expects to support it in FRx 6.7 SP 11, due out in July, 2009.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;When might I recommend SQL Server 2008?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;SQL Server 2008 has a number of new features that some clients my find compelling including:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;A (reported) 10-15% performance improvement over SQL Server 2005.&lt;/li&gt;    &lt;li&gt;Transparent Data Encryption that encrypts the entire database, log and backup files.&amp;#160; This feature might be of interest to clients that store credit card numbers, 1099 vendor SSNs, employee payroll information etc. and may be subject to Payment Card Industry Data Security Standards (PCI DSS) or the Mass Privacy regulations going into affect 1/1/2010.&lt;/li&gt;    &lt;li&gt;Built in data (including data viewing) and configuration auditing for clients that might face compliance issues (PCI, SOX, HIPPA, Mass Privacy)&lt;/li&gt;    &lt;li&gt;Completely rewritten SQL Server Reporting Services with improved performance (particularly multi-user performance) and new more user-friendly report builder for report authoring.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;What about Windows Server 2008?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;Like SQL Server 2008, Windows Server 2008 has passed the initial service pack test (it’s currently on SP2) and so many clients may be migrating to it or at least setting up new hardware/VMs with it.&amp;#160;&amp;#160;&amp;#160; Like SQL Server 2008, some components, most notably FRx are not supported on Windows Server 2008.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;Can you give me a quick reference on component support?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;Sure.&amp;#160; The following components are supported on 32bit only.&amp;#160; Thus if you use any of these components, they must be installed on a 32bit OS, ideally an app server:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Rapid Implementation Tools (including Rapid Configuration and Rapid Migration)&lt;/li&gt;    &lt;li&gt;Business Portal for Microsoft Dynamics GP 10.0, Internet Information Services server&lt;/li&gt;    &lt;li&gt;Catalog Maintenance&lt;/li&gt;    &lt;li&gt;Microsoft Dynamics Security Synchronization Utility&lt;/li&gt;    &lt;li&gt;Business Portal Migration Utility&lt;/li&gt;    &lt;li&gt;Analysis Cubes for SQL Server 2000 (client and server).&amp;#160; &lt;i&gt;Note that this is for &lt;b&gt;SQL Server 2000&lt;/b&gt;.&amp;#160; The Analysis Cubes for SQL Server 2005 are supported on 64bit.&lt;/i&gt;&lt;/li&gt;    &lt;li&gt;Benefit Self Service Suite and Certification, Licensing, and Training Manager for Business Portal&lt;/li&gt;    &lt;li&gt;Personal Data Keeper&lt;/li&gt;    &lt;li&gt;Workflow&lt;/li&gt;    &lt;li&gt;Workflow Software Development Kit&lt;/li&gt;    &lt;li&gt;Workflow History Archiving Utility&lt;/li&gt;    &lt;li&gt;Microsoft FRx Reporter 6.7&lt;/li&gt;    &lt;li&gt;Microsoft FRx Forecaster 6.7&lt;/li&gt;    &lt;li&gt;Microsoft FRx Web Port 6.7&lt;/li&gt;    &lt;li&gt;Enterprise Reporting 7.5&lt;/li&gt;    &lt;li&gt;SmartTags&lt;/li&gt;    &lt;li&gt;eExpense&lt;/li&gt;    &lt;li&gt;Field Service Anywhere&lt;/li&gt;    &lt;li&gt;Microsoft Dynamics Solution Accelerator for Compliance Management&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The following components are not supported on Windows Server 2008 (&lt;b&gt;&lt;i&gt;Assume SP3 for all supported components&lt;/i&gt;&lt;/b&gt;).&amp;#160; Thus if you use any of these components, the app server must be running Windows 2003:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Rapid Implementation Tools (including Rapid Configuration and Rapid Migration)&lt;/li&gt;    &lt;li&gt;Analysis Cubes for SQL Server 2000 (client and server).&amp;#160; &lt;i&gt;Note that this is for &lt;b&gt;SQL Server 2000&lt;/b&gt;.&amp;#160; The Analysis Cubes for SQL Server 2005 are supported on 64bit.&lt;/i&gt;&lt;/li&gt;    &lt;li&gt;Workflow.&amp;#160; Note that Workflow will be supported with the release of Microsoft Office SharePoint Server Service Pack 2.&amp;#160; &lt;/li&gt;    &lt;li&gt;Microsoft FRx Reporter 6.7.&amp;#160; &lt;i&gt;Note that it is expected to be supported on the release of SP 11.&lt;/i&gt;&lt;/li&gt;    &lt;li&gt;Microsoft FRx Forecaster 6.7&lt;/li&gt;    &lt;li&gt;Microsoft FRx Web Port 6.7&lt;/li&gt;    &lt;li&gt;Enterprise Reporting 7.5&lt;/li&gt;    &lt;li&gt;SmartTags&lt;/li&gt;    &lt;li&gt;Field Service Anywhere&lt;/li&gt;    &lt;li&gt;Microsoft Dynamics Solution Accelerator for Compliance Management&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The following components are not supported on SQL Server 2008 (&lt;b&gt;&lt;i&gt;Assume SP3 for all supported components&lt;/i&gt;&lt;/b&gt;).&amp;#160; Thus if you use any of these components, you must use SQL Server 2005 (64bit or 32bit is fine):&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Rapid Implementation Tools (including Rapid Configuration and Rapid Migration)&lt;/li&gt;    &lt;li&gt;Analysis Cubes for SQL Server 2000 (client and server).&amp;#160; &lt;i&gt;Note that this is for &lt;b&gt;SQL Server 2000&lt;/b&gt;.&amp;#160; The Analysis Cubes for SQL Server 2005 are supported on 64bit.&lt;/i&gt;&lt;/li&gt;    &lt;li&gt;Microsoft FRx Reporter 6.7.&amp;#160; &lt;i&gt;Note that it is expected to be supported on the release of SP 11.&lt;/i&gt;&lt;/li&gt;    &lt;li&gt;Microsoft FRx Forecaster 6.7&lt;/li&gt;    &lt;li&gt;Microsoft FRx Forecaster 7.0&lt;/li&gt;    &lt;li&gt;Microsoft FRx Web Port 6.7&lt;/li&gt;    &lt;li&gt;Enterprise Reporting 7.5&lt;/li&gt;    &lt;li&gt;SmartTags&lt;/li&gt;    &lt;li&gt;Field Service Anywhere&lt;/li&gt;    &lt;li&gt;Microsoft Dynamics Solution Accelerator for Compliance Management&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;b&gt;&lt;i&gt;Can you give me a quick summary of the environments?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;Absolutely.&amp;#160; Here they are:&lt;/p&gt;  &lt;p&gt;&lt;b&gt;Recommended Standard Environment:&lt;/b&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;2 Servers&lt;/li&gt;    &lt;li&gt;Server 1 (SQL Server) – Windows Server 2003 or 2008 64bit, SQL Server 2005 (or 2008 based on compatibility) 64bit.&lt;/li&gt;    &lt;li&gt;Server 2 (Application Server) – Windows Server 2003 32bit, Dynamics GP full client install (“admin” or “server” install), all required components (FRx, eConnect, Web Services), Microsoft Office (clients current version), DynData file share, SQL Server Client Tools (SQL Server Management Studio, SQL Server Business Intelligence Studio).&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Low Volume Environment:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;1 Server – Windows Server 2003 32bit, SQL Server 2005 32bit, Dynamics GP full client install (“admin” or “server” install), all required components (FRx, eConnect, Web Services), Microsoft Office (clients current version), DynData file share.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;High Transaction Volume Environment:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;3 Servers&lt;/li&gt;    &lt;li&gt;Server 1 (SQL Server) – Windows Server 2003 or 2008 64bit, SQL Server 2005 (or 2008 based on compatibility) 64bit &lt;b&gt;&lt;i&gt;database engine only.&amp;#160; Do not install Reporting Services or Analysis Services.&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;    &lt;li&gt;Server 2 (Application Server) – Windows Server 2003 32bit, Dynamics GP full client install (“admin” or “server” install), all required components (FRx, eConnect, Web Services), Microsoft Office (clients current version), DynData file share, SQL Server Client Tools (SQL Server Management Studio, SQL Server Business Intelligence Studio).&lt;/li&gt;    &lt;li&gt;Server 3 (Reporting &amp;amp; Analysis Server) – Windows Server 2003 or 2008 64bit, SQL Server 2005 Reporting Services(or 2008 based on compatibility) 64bit (32bit if reporting from external 32bit ODBC sources such as MySQL or MS Access), SQL Server Analysis Services, SQL Server Integration Services.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;High Transaction Volume/High BI Volume Environment:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;4 Servers&lt;/li&gt;    &lt;li&gt;Server 1 (SQL Server) – Windows Server 2003 (or 2008 based on compatibility) 64bit, SQL Server 2005 (or 2008 based on compatibility) 64bit &lt;b&gt;&lt;i&gt;database engine only.&amp;#160; Do not install Reporting Services or Analysis Services.&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;    &lt;li&gt;Server 2 (Application Server) – Windows Server 2003 32bit, Dynamics GP full client install (“admin” or “server” install), all required components (FRx, eConnect, Web Services), Microsoft Office (clients current version), DynData file share, SQL Server Client Tools (SQL Server Management Studio, SQL Server Business Intelligence Studio).&lt;/li&gt;    &lt;li&gt;Server 3 (Reporting Server) – Windows Server 2003 or 2008 64bit, SQL Server 2005 Reporting Services(or 2008 based on compatibility) 64bit (32bit if reporting from external 32bit ODBC sources such as MySQL or MS Access).&lt;/li&gt;    &lt;li&gt;Server 4 (Analysis Server) –Windows Server 2003 or 2008 64bit, SQL Server Analysis Services, SQL Server Integration Services.&lt;/li&gt; &lt;/ul&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-4115129636080869978?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/4115129636080869978/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/dynamics-gp-sql-2008-windows-2008-and.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4115129636080869978'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4115129636080869978'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/07/dynamics-gp-sql-2008-windows-2008-and.html' title='Dynamics GP, SQL 2008, Windows 2008 and 64bit'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-2340588469105291061</id><published>2009-06-09T13:10:00.001-07:00</published><updated>2009-06-09T13:10:07.340-07:00</updated><title type='text'>Dynamics GP and SQL Server 2008 Reporting Services</title><content type='html'>&lt;p&gt;As of Dynamics GP 10.00 Service Pack 3, SQL Server 2008, including Reporting Services is now supported.&amp;#160; One little item to note however, is that in the Reporting Tools Setup, Reporting Services Tab, the Report Server URL needs to reference the ReportService2005.asmx web service file instead of the ReportService.asmx file as the ReportService.asmx file no longer exists.&lt;/p&gt;  &lt;p&gt;As such the URL should be:&lt;/p&gt;  &lt;p&gt;http://MySSRSServer/ReportServer/ReportService2005.asmx&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-2340588469105291061?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/2340588469105291061/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/06/dynamics-gp-and-sql-server-2008.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/2340588469105291061'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/2340588469105291061'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/06/dynamics-gp-and-sql-server-2008.html' title='Dynamics GP and SQL Server 2008 Reporting Services'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-4790867401421488595</id><published>2009-06-09T11:52:00.001-07:00</published><updated>2009-06-09T11:52:20.681-07:00</updated><title type='text'>Quick ODBC Administrator Tip When Installing Dynamics GP on 64bit Systems</title><content type='html'>&lt;p&gt;We’ve started to run across clients requesting that we install Dynamics GP clients on systems with 64bit operating systems.&amp;#160; One of the first issues we came across was not being able to find the Dynamics ODBC DSN.&amp;#160; A DSN created by the Dynamics GP install was not visible in the ODBC Administrator tool and a DSN created in the Administrator tool was not visible to Dynamics.&lt;/p&gt;  &lt;p&gt;It turns out that the default ODBC Administrator tool under the Windows Administrative Tools menu on a 64bit OS is the 64bit version for 64bit ODBC.&amp;#160; Since Dynamics is still a 32bit app we need to find and run the 32bit ODBC Administrator tool.&amp;#160; The executable is Odbcad32.exe and is located in the %systemdrive%\Windows\SysWoW64 folder.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-4790867401421488595?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/4790867401421488595/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/06/quick-odbc-administrator-tip-when.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4790867401421488595'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/4790867401421488595'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/06/quick-odbc-administrator-tip-when.html' title='Quick ODBC Administrator Tip When Installing Dynamics GP on 64bit Systems'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-5308946715920986996</id><published>2009-06-03T14:43:00.001-07:00</published><updated>2009-06-03T15:30:27.945-07:00</updated><title type='text'>eConnect + Custom Web Service vs. Dynamics GP Web Services</title><content type='html'>&lt;p&gt;Recently, I’ve begun questioning whether I should bother using the Dynamics GP Web Services or simply write my own against eConnect.&amp;#160; In the past I’ve done both.&amp;#160; I’ve written several integrations using the Dynamics GP Web Services.&amp;#160; I’ve also written a couple where I wrote my own custom web service against eConnect.&amp;#160; Specifically, there were cases where I needed to develop GL integrations that used Analytical Accounting, functionality provided by eConnect but not Web Services.&lt;/p&gt;  &lt;p&gt;My latest conundrum has come about because I have a client I’ve written three integrations for using Web Services.&amp;#160; An enhancement they’ve asked for requires me to integrate invoices and returns and then apply them to each other.&amp;#160; It appears that this is doable through the taRMApply node (though I haven’t had a chance to try it yet) but not available through Web Services.&lt;/p&gt;  &lt;p&gt;So now it looks like I need to create a custom web service, outside of the Dynamics GP Web Service security domain, to simply implement the apply functionality.&amp;#160; Beyond the missing eConnect functionality I’ve developed a few other pet peeves:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;I have a &amp;lt;50% success rate on a first time install of Web Services.&amp;#160; Sometimes a repair or reinstall will work.&amp;#160; Often a support ticket on PartnerSource is required (I’ve noted that when entering a PartnerSource support ticket Web Services Installation is now a separate category from Web Services).&lt;/li&gt;    &lt;li&gt;I can’t add an Active Directory group to a role assignment.&amp;#160; I must add individual users.&lt;/li&gt;    &lt;li&gt;You must pass complex objects to the methods so they can’t easily be called by InfoPath or SharePoint Designer workflows.&amp;#160; OK, I can’t really be too hard on them for this as I’m not sure how you easily generically build a web service to allow an integration to pass an AP document header and distributions in a simple web method, but still it means I’m writing my own web services.&lt;/li&gt;    &lt;li&gt;Performance, especially that first call, leaves something to be desired.&lt;/li&gt;    &lt;li&gt;Significantly less extensibility than eConnect and as far as I can tell unable to make use of any eConnect extensions.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;OK, so when it does meet your requirements it works quite well.&amp;#160; It also has an exceptionally robust role based security model I certainly wouldn’t want to have to reproduce, but quite frankly it’s overkill for what I need it for.&lt;/p&gt;  &lt;p&gt;So I think for now I’m going to lean towards a roll your own web service model on top of eConnect and wait to see what GP 11 holds in store.&lt;/p&gt;  &lt;p&gt;Let me know what your thoughts are on this.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-5308946715920986996?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/5308946715920986996/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/06/econnect-custom-web-service-vs-dynamics.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/5308946715920986996'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/5308946715920986996'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/06/econnect-custom-web-service-vs-dynamics.html' title='eConnect + Custom Web Service vs. Dynamics GP Web Services'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-1416489603215227664</id><published>2009-06-03T10:03:00.001-07:00</published><updated>2009-06-03T11:23:47.361-07:00</updated><title type='text'>Securing Dynamics GP with SQL Server 2008</title><content type='html'>&lt;p&gt;I recently co-authored a Microsoft sponsored whitepaper with Cynthia Papas of the accounting firm &lt;a href="http://www.parentenet.com" target="_blank"&gt;Parente Randolph&lt;/a&gt; on how to deploy and configure SQL Server 2008 in a compliance with the &lt;a href="https://www.pcisecuritystandards.org" target="_blank"&gt;Payment Card Industry Data Security Standards&lt;/a&gt; (PCI DSS) (click &lt;a href="http://www.parentenet.com/news/0904_whitepaper.pdf" target="_blank"&gt;here&lt;/a&gt; to download the whitepaper and &lt;a href="https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?culture=en-US&amp;amp;EventID=1032404174&amp;amp;CountryCode=US" target="_blank"&gt;here&lt;/a&gt; to replay the TechNet webcast).&amp;#160; &lt;/p&gt;  &lt;p&gt;Although this paper is about SQL 2008 in general and PCI DSS in particular, I wondered how it might apply to my Dynamics GP clients.&amp;#160; The reality is that few of them do credit card processing out of Dynamics GP and so don’t need to comply with PCI DSS.&amp;#160; However, here in Massachusetts we’ve enacted the Mass. Privacy regulations that go into affect 1/1/2001 and cover the protection of personal information such as Social Security Numbers, driver’s license numbers, bank account numbers etc.&amp;#160; Many other states have similar regulations (I believe the Mass. ones are based on California’s).&amp;#160; In addition there’s HIPPA, SOX, and on and on.&amp;#160; And the reality is is that it’s just good business to protect your customer, vendor and employee data.&lt;/p&gt;  &lt;p&gt;So other than credit card numbers, what sort of data might be sitting in GP to protect?&amp;#160; A couple of months ago I had to fix a companies 1099 data.&amp;#160; Stored in the Vendor master table were hundreds of 1099 vendors with their unencrypted SSNs.&amp;#160; Same would be true if you’re running payroll through GP.&lt;/p&gt;  &lt;p&gt;If you’re a healthcare provider and billing out of GP, in all likelihood there’s medical data in those invoices that come under the jurisdiction of HIPPA.&lt;/p&gt;  &lt;p&gt;If you use EFT with your customers or vendors you’re storing&amp;#160; bank account numbers for them.&lt;/p&gt;  &lt;p&gt;Most of these regulations deal with not only the protection (access control and encryption) of the data but also auditing the viewing of the data.&amp;#160; SQL 2008 helps on both these fronts.&lt;/p&gt;  &lt;p&gt;First, with Transparent Data Encryption (TDE), SQL will encrypt the entire database, as well as log and backup files, in a way that the consuming application (in this case Dynamics GP), need know nothing about.&amp;#160; &lt;/p&gt;  &lt;p&gt;Additionally, SQL 2008 offers built-in auditing, including audits on SELECT statements (no need to use SQL Profiler).&amp;#160; Unlike most homegrown or off the shelf auditing tools, SQL 2008’s auditing capabilities are not based on triggers and work asynchronously, thus having a minimal impact on performance.&lt;/p&gt;  &lt;p&gt;There’s more detail in the whitepaper as well as on Microsoft’s site and other third party sites.&amp;#160; The thrust of this entry is to get you thinking about whether there’s data in your GP databases that needs to be protected and might it be worth considering an upgrade to SQL 2008 to do that given the risk of identity theft and its associated liabilities.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Support Note:&amp;#160; &lt;/strong&gt;To my knowledge, only Dynamics GP 10.0 SP2 and above supports SQL Server 2008.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-1416489603215227664?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/1416489603215227664/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/06/securing-dynamics-gp-with-sql-server.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/1416489603215227664'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/1416489603215227664'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/06/securing-dynamics-gp-with-sql-server.html' title='Securing Dynamics GP with SQL Server 2008'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-5442631054006844855</id><published>2009-05-18T13:39:00.001-07:00</published><updated>2009-05-18T14:39:31.258-07:00</updated><title type='text'>Changing the Server Name of your SQL Server</title><content type='html'>&lt;p&gt;In this day and age of virtualization, it’s not uncommon to copy a base VM, rename it and away you go.&amp;#160; But what if SQL Server 2005 is already installed on it?&amp;#160; How does renaming affect it?&lt;/p&gt;  &lt;p&gt;At first glance it may appear that it has had no affect at all, queries will still work, logins still work.&amp;#160; However, there are a few subtle and not so subtle side affects.&amp;#160; The first is what SQL Server thinks it’s name is.&amp;#160; If you run the following statement:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT @@SERVERNAME&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;You’ll notice that it returns the old server name, not the new one.&amp;#160; This happens even though the name shown in the Object Explorer in Management Studio is showing the correct new name.&amp;#160; This will cause a clean Dynamics GP 10 install to fail when attempting to create the “Remove Posted PJOURNALs From All Companies” SQL Agent job as it uses the @@SERVERNAME variable to set that up.&lt;/p&gt;  &lt;p&gt;To configure SQL Server to know its new name you’ll have to run the following two statements:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;EXEC sp_dropserver 'OLDSERVERNAME'     &lt;br /&gt;EXEC sp_addserver 'NEWSERVERNAME', local&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Once you’ve run these statements, restart the SQL Server service.&amp;#160; This will cause SQL Server to now return the new server name from the @@SERVERNAME variable.&lt;/p&gt;  &lt;p&gt;A more noticeable side affect of the server name change is that SQL Reporting Services (SSRS) will not work.&amp;#160; This is because it is looking to the old server name for the reporting services databases (ReportServer and ReportServerTempDB).&amp;#160; To modify this, perform the following steps:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Run the Reporting Services Configuration tool (Start/All Programs/Microsoft SQL Server 2005/Configuration Tools/Reporting Services Configuration).&lt;/li&gt;    &lt;li&gt;Navigate to the “Database Setup” page.&lt;/li&gt;    &lt;li&gt;Change the Server Name to the new server name and click Apply.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;SSRS will now work.&lt;/p&gt;  &lt;p&gt;Now what about changing domains?&amp;#160; That’ll have to be another post.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-5442631054006844855?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/5442631054006844855/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/05/changing-server-name-of-your-sql-server.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/5442631054006844855'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/5442631054006844855'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/05/changing-server-name-of-your-sql-server.html' title='Changing the Server Name of your SQL Server'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2681714432624335789.post-1975205579380411937</id><published>2009-05-07T13:39:00.001-07:00</published><updated>2009-10-28T13:34:39.020-07:00</updated><title type='text'>Run a Script Against Multiple Companies by Creating SQL from SQL</title><content type='html'>&lt;p&gt;This subject isn't the grand topic I had envisioned for my initial post but is a neat little trick I think many would find useful. So here goes the blog!&lt;/p&gt;  &lt;p&gt;We recently had to create users and assign SQL permissions to over 200 Dynamics GP companies for an integration. The integration is external to the Dynamics GP client and accesses the SQL databases directly rather than through the client, so SQL security had to be setup. There are a couple of options:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Click through SQL Management, creating users and assigning to the appropriate role (very tedious). &lt;/li&gt;    &lt;li&gt;Create a SQL script that sets up the security and run it against each company database (slightly less tedious). &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Let’s rule out the first option right away. With the second option, the script might look something like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;CREATE USER [MYDOMAIN\INTEGRATION USERS] FROM LOGIN ''MYDOMAIN\INTEGRATION USERS'' &lt;/p&gt;    &lt;p&gt;GO &lt;/p&gt;    &lt;p&gt;EXEC sp_addrolemember [Integration Users], [MYDOMAIN\INTEGRATION USERS] &lt;/p&gt;    &lt;p&gt;GO&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here we’re creating a user in the database and adding it to the appropriate role. However, we still have to run it against each company databases. Pick database from dropdown, click execute, wait, repeat.&lt;/p&gt;  &lt;p&gt;If only there were an easy way to generate a script that would run against each database.&lt;/p&gt;  &lt;p&gt;Well there is! With a simple SQL statement we can generate a script that will run against every company.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;--&amp;#160; Don't forget to set Results to Text in your     &lt;br /&gt;--&amp;#160; SQL Management Studio query window.      &lt;br /&gt;--      &lt;br /&gt;--&amp;#160; Set NOCOUNT on so that row count doesn't show      &lt;br /&gt;--&amp;#160; up in the text results.      &lt;br /&gt;SET NOCOUNT ON; &lt;/p&gt;    &lt;p&gt;SELECT     &lt;br /&gt;REPLACE(      &lt;br /&gt;'      &lt;br /&gt;&lt;font color="#ff0000"&gt;USE {COMPANYDB}       &lt;br /&gt;GO &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;CREATE USER [MYDOMAIN\INTEGRATION USERS] FROM LOGIN [MYDOMAIN\INTEGRATION USERS] &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;GO &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;EXEC sp_addrolemember ''Integration Users'', ''MYDOMAIN\INTEGRATION USERS'' &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font color="#ff0000"&gt;GO       &lt;br /&gt;&lt;/font&gt;',      &lt;br /&gt;'{COMPANYDB}', INTERID)      &lt;br /&gt;FROM DYNAMICS.dbo.SY01500&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;In the SQL statement above we’ve taken our security script, added a USE statement at the top to set the company and placed it inside a SQL statement. Note that the USE statement has tag {COMPANYDB} instead of a SQL database name. The SELECT statement replaces this with the INTERID from the SY01500 GP companies table in the DYNAMICS database. &lt;/p&gt;  &lt;p&gt;This statement will create a single row for each company containing a single column with the resulting security script. When concatenated together you get the following script (this was run against a GP instance with two companies):&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;USE AAA     &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;CREATE USER [MYDOMAIN\INTEGRATION USERS] FROM LOGIN [MYDOMAIN\INTEGRATION USERS]&lt;/p&gt;    &lt;p&gt;GO &lt;/p&gt;    &lt;p&gt;EXEC sp_addrolemember 'Integration Users', 'MYDOMAIN\INTEGRATION USERS' &lt;/p&gt;    &lt;p&gt;GO &lt;/p&gt;    &lt;p&gt;USE TWO     &lt;br /&gt;GO &lt;/p&gt;    &lt;p&gt;CREATE USER [MYDOMAIN\INTEGRATION USERS] FROM LOGIN [MYDOMAIN\INTEGRATION USERS]&lt;/p&gt;    &lt;p&gt;GO &lt;/p&gt;    &lt;p&gt;EXEC sp_addrolemember 'Integration Users', 'MYDOMAIN\INTEGRATION USERS' &lt;/p&gt;    &lt;p&gt;GO&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;To get this to work in SQL Management Studio we need to send the results to text instead of the grid. This can be set in the Query/Results To menu as shown here&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh6.ggpht.com/_M9HO4tQKytY/SgNG_vAMtfI/AAAAAAAAABo/TYaAFriDUhY/s1600-h/image4.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="332" alt="image" src="http://lh6.ggpht.com/_M9HO4tQKytY/SgNHAPs6CiI/AAAAAAAAABs/d10Vi9ftncM/image_thumb2.png?imgmax=800" width="419" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;or clicking on the To Text button in the tool bar shown below:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh6.ggpht.com/_M9HO4tQKytY/SgNHAVCdrcI/AAAAAAAAABw/Ef0yLr6DFuA/s1600-h/image7.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="77" alt="image" src="http://lh5.ggpht.com/_M9HO4tQKytY/SgNHAtD_9aI/AAAAAAAAAB0/JmD9W_dzxVc/image_thumb3.png?imgmax=800" width="122" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Note that by default, the maximum number of characters show column per row when sending results to text is 256. This can be changed in Tools/Options screen, navigating to Query Results/SQL Server/Results to Text and updating the Maximum number of characters in each column to the maximum number of characters in your company script.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_M9HO4tQKytY/SgNHBOrx94I/AAAAAAAAAB4/V4YQWohJA_Q/s1600-h/image11.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="284" alt="image" src="http://lh5.ggpht.com/_M9HO4tQKytY/SgNHBTiM5XI/AAAAAAAAAB8/cikGTMZbhvI/image_thumb9.png?imgmax=800" width="467" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;This trick could be used for any number of tasks that need to be run against every company such as creating custom stored procedures or performing mass backups and restores into test environments.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2681714432624335789-1975205579380411937?l=dynamicsgpgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dynamicsgpgeek.blogspot.com/feeds/1975205579380411937/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/05/run-script-against-multiple-companies.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/1975205579380411937'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2681714432624335789/posts/default/1975205579380411937'/><link rel='alternate' type='text/html' href='http://dynamicsgpgeek.blogspot.com/2009/05/run-script-against-multiple-companies.html' title='Run a Script Against Multiple Companies by Creating SQL from SQL'/><author><name>John "Limey" Bastow</name><uri>http://www.blogger.com/profile/11228935618264612784</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_M9HO4tQKytY/SgNHAPs6CiI/AAAAAAAAABs/d10Vi9ftncM/s72-c/image_thumb2.png?imgmax=800' height='72' width='72'/><thr:total>2</thr:total></entry></feed>
