Matt Evans's Weblog

  • Axapta Document Management

    I've recently become involved in testing the Document Management features in Axapta.  This is a pretty powerful subsystem for integrating external structured and unstructured data and document types with the in built ERP system.

     There are two sides of document management... the end-user aspect and the administrator or setup side of it.  Ultimately what you want is to give your end users a better experience; I'll mostly be talking about the sorts of documents you can setup using the administration side of things.

    User Overview of Attaching Documents 

    To get a quick look at doc handling in Axapta, open up AR->Customers.  Next to the big "bell" icon (the Alerts button, if you're using 4.x) there will be an icon that looks like a document.  If you mouse-hover (in 4.x, at least) over the button it will say "Document handling"

    If you click this button you get the Document Handling form (DocuView). 

    DocuView is an app-singleton form.  That means that you can only have one instance of it open at a time.  Since you open DocuView from some other form, you are operating on a linked data source (that is, the DocuView form knows who called it, and restricts its data to the context-appropriate records).  In our case, you'll see the documents attached to whatever customer was selected when you hit the doc handling button.

    If you're new to doc handling, you'll probably have an empty grid.    Off on the right you'll see a "New" button.  Axapta has some in-built document types; the most commonly used one is "Note".  Select New->Note.  You'll be able to enter a description, and then set the flag "Internal" or "External".  Finally, in the window at the bottom of the DocuView form you'll be able to enter free form text.

    hit ctrl-S to save the note.  You've now attached a document to this customer.

    Administrator view of Setting up Document Types 

    Now, you may have noticed other options available to you under New->.  On my configuration, one of them is "Letter". 

    Axapta has the ability to create Word documents for you with data pre-populated.  Here we begin to talk about the Setup side of things.

    In Basic->Setup->Document Management->Document Types, you'll find the configuration of the document types available in Doc handling. 

    You can create new records here.  Give your new document type a name like "CustLetter".  The "Job description" property is most interesting.  Here we'll choose from the drop down "Create Word document via COM".

    Switch over to the general tab and make sure to set the "Archive directory".  Then click "Options" on the right.

    Mapping Axapta Table Fields to Document Template Bookmarks

    The new form that opens is how you bind Axapta data to the document type.  Here you'll define what data to export into the Word document, and how it will be mapped into the document.

    Hit Ctrl-N to setup a new table mapping.  Choose the table you'd like to pull data from (CustTable is a fine choice). 

    Here we're going to use a Word Template. 

    Suppose I've got a word document that is a nice form letter I'd like to send customers.  It has my company logo on it, some standard legal text, my return address, and so on.  I'd like to auto-populate regions of this letter with data like the customers name and address, and then store the resultant letter in the doc management system.

    Here's how we can use Doc handling to automate this (to a degree).

    Create a word document that looks like you'd like the letter to appear.  For places where you'd like the customers address or name, you'll insert word Bookmarks.  In Word 2007, you can insert a Bookmark right from the ribbon bar... Insert->Links->Bookmark.  Move the cursor in the document to the position you'd like to insert the customer name, and then insert a bookmark.  Name the bookmark "CustName". 

    You'll want to save this file as a word template, by going to File->Save As, and changing the type.  The template lets you control content and formatting, and is used as a starting point from which to generate new word documents that have the bookmarks filled in with real customer data.

    Back in AX on the Setup form, edit the "Template File" column to point at your newly saved template.

    Now flip over to the "fields" tab on this form.  Here you can hit Ctrl-N to add new Field->Bookmark mappings.  Create a mapping from data field "name" on table "CustTable", to the bookmark "CustName".  Save your configuration and then save/click OK to all of the setup forms you've got open. 

    Now we're ready to try it out!

    If you open up a customer, click doc handling, and then click "New->CustLetter".  Word should fire up, show you a new document named according to the configured number sequence, and the document should have the content of the template you created earlier, with the customer's name filled in where you defined the bookmark.  Save the document and you're done!

    Wrap Up

    Hopefully you were able to setup a new document type that exported table data to a word doc based on a template without too much trouble.  Templates can be reasonably complex, and data can come from multiple tables, but there are some limitations. 

  • HoeKey - Exactly what I needed

    So I listen to music at work a bunch via headphones (here in Fargo, we've got cubes, not individual offices).  I use my laptop as my "fluff" machine - that is, i listen to music, read mail, do office stuff, write up bugs, web surf (gasp!), etc from it. 

    Naturally, this doesn't have the "media" keys of the recently awful MS keyboards.  So the common scenario is that a person comes over to talk to me and i have to find media player, restore it, hit pause, then take off my headphones, then turn to talk to the person.

    What i wanted was a global "pause" button.

    It turns out that somebody has made a tiny (12kb) program that lets you bind arbitrary key sequences (context-free, it seems) to arbitrary actions.  This thing fits the bill exactly, and is available from http://dana.ucc.nau.edu/~tsr22/apps/hoekey.htm.  It has stuff built-in to send messages to whatever app, so i can bind windowskey-P to send the play/pause command to WMP.  Life is good.

    As an aside, the default config includes "windowskey-4" as a command which..turns the currently selected window to half alpha-transparency.

     

  • iPod - Incompatable by Design.

    A coworker here was thinking about getting an iPod Nano.  My best friend ordered one the day they were announced and I got to see it a bit this weekend.  Side note - we were at a resturant and I was playing with it, and the waitress came over to talk to us about it and was telling us how she just got hers and totally loves it, and wanted to know if i liked mine, to which i sheepishly replied that it belonged to my friend. 

    In any case, the thing is tiny.  The display is beautiful, the interface works how you'd expect.  It has the ability to store vCards and other cool stuff.

    So my co-worker asked "do you need iTunes to sync to it?".  I didn't know.  My friend figured you didn't, and that it just showed up as a mass storage device.  Sweet.  But before I told my coworker about this, i figured i had better look into it myself.

    So later this weekend i hooked the thing into my XP SP2 machine via USB cable.  It showed up as a disk, and when I went to explore that disk, it had folders for eTexts, vCards, and calender appointments.  Nothing about music.  There is a hidden folder called something like "itunes data" and if you explore it, you find a few index files and album art collections and other stuff, and eventually you get to a folder that contains what appear to be the song files - they have .mp3 and .aac (or whatever they are) extensions on them.

    The problem is that these files all have 4 letter names.. there's no effective way to look through this folder and figure out what the songs are.  The containing folder heirarchy is also broken down into arbitrary folders.. something like M1 through M6 on this particular unit. 

    The ipod apparently has a media database that it uses to let you navigate its menus, and as part of putting songs on it, you're updating this database, and during that time, turning the proper filenames into hash names and then recording those hashnames in the database.

    While i can understand the design decision to have an offboard index calculator (itunes), it stinks that getting media into or out of this thing is more difficult than it needs to be.

    I can think of two or three people that would be more interested in the iPod Nano if they didn't need to use iTunes to deal with it.  Not that Apple should worry about what 2 or 3 people want, but I suspect there are more pro-Ipod, anti-iTunes people out there.

     

  • Make SQL Server generate SQL for you using SELECT literals

    Suppose that I've got an instance of SQL server setup for testing, and I've programmatically created databases on this instance, but never cleaned them up.

    This can leave you with hundreds of databases that you'll never use again, and there's no reason for them to continue taking up space and cluttering things which you do not wish to be cluttered.

    Now, you could use a GUI tool like enterprise manager, and right click on every database and choose "Delete"

    Obviously, that stinks.

    Alternatively, you could use isqlw (SQL Query Analyzer) and enter the appropriate T-SQL statement to delete a database:

    drop database Foo 
    go 

    This also stinks, because thats a lot of typing, once you do it for 100 databases

    Fortuneately, you can do better. What you've got, fundamentally, is a problem that computers are good at. You've got a lot of somethings (databases) that you want to do something to. You know what you want to do - drop a database. Now you just need to leverage that across a big list of them.

    If one wanted to get a list of data in SQL, one would write a a SELECT statement. It turns out that if we're willing to look at the system tables, sql server will tell us about the databases installed in this instance.

    select name 
    from sysdatabases 
    where sid <> 0x01 
    and name like 'HW_%' 

    Now, this will give you the name of any databases from this instance which match "HW_%" and which have an sid other than 0x01. The sid <> 0x01 clause is interesting. On the SQL instances I'm looking at, the "system" databases always have sid=0x01. But on one other instance, Northwind also has sid=0x01. User databases always have a long gooofy, non-0x01 value for sid. So you can use this as a hack to do a "reasonable" job of excluding "system" databases from your select list. If you can make a determination based on the dbname, as I have above, you can do even better.

    Great, we can get SQL server to tell us the list of user databases installed from this instance. What does that buy me? Well, suppose we change the select list:

    select 'drop database ', name 
    from sysdatabases 
    where sid <> 0x01 
    and name like 'HW_%' 

    If you run this select statement (and you have databases that match the select list) you'll get the following output (at least on my machine - you probably won't get this output as your dbnames will be different

    drop database HW_North 
    drop database HW_South 
    drop database HW_Enterprise 

    Congratulations, you've just told SQL server to generate SQL for you. Obviously, if you had 100 databases in your select list, you'd get 100 statements generated. Here's where the hack comes in, i don't know of a way to actually execute this in one step, but I bet it is possible. Instead, I copy and paste the results (results to grid works fine in ISQLW, results as text will give you a giant column width for the name column), back into a command window, and then execute the batch.

    This technique is handy for dropping lots of databases, but its also good in other places. For instance, you can generate INSERT or UPDATE statements this way, which may be desirable if you've got just one or two peices that make doing the job with a single insert/update too difficult.

    It is not elegant, not efficient, and probably upsets somebody somewhere, but for a quick and dirty management task, using select literals to generate statements for you is a great technique to keep in mind.

  • Don't trust user supplied data - Real World Example

    Suppose I have a notion of a User Account in my application, and I want to allow 3rd parties to tie that user account to one or more objects in an external directory/authentication system. 

    Let's say, for the sake of argument, that I'm going to use System.DirectoryServices.DirectoryEntry to represent links to external directory objects. 

    I need to persist the relationship between a DE and my concept of a User. 

    The way I've chosen to do this is by taking the Path Property  of the DirectoryEntry and saving that, as a string, into the database.

    When I need to re-hydrate my user Account object, I'll back the DirectoryEntry property by using the DirectoryEntry constructor which takes the Path as its argument.

    Now, this is all well and good, except what it means is that anytime anyone loads an instance of my User Account, and looks at the DirectoryEntry property (say, a user management application), they're going to be running code that I supplied data to.  Namely, the constructor code for the DE class.

    We're now in a situation where we're running code, and passing it an argument from an untrusted source. 

    DE is currently a shim on top of the COM based ADSI architecture.  ADSI is a scheme with multiple, pluggable providers.  The DE Path specifies the provider to use.

    This is nice from a programming perspective - if you want to get an Active Directory object corresponding to a user, you might try:

    de = new DirectoryEntry(”LDAP://CN=mattev,DC=microsoft,DC=com”);

    alternatively, if you had an NT4 provider:

    de = new DirectoryEntry(”WinNT://localhost/administrator”);

    You've got the same .NET DirectoryEntry object, and the same ADSI COM interface, but underneath you've got an LDAP provider handling one LDAP object, and a WinNT provider handling the other.

    Now, it turns out that there's a 3rd “standard” provider -the IIS:// provider.  This provider lets you use ADSI to script commands against the IIS metabase.  The IIS ADSI interface is extensive - you can create virtual directories, delete them, delete metabase backup copies, and so on.  Essentially any IIS admin task can be performed via the IIS ADSI interface.

    Note that any of these IIS:// type paths are perfectly valid DirectoryEntry paths - Infact, only the IIS provider can say if an IIS:// path isn't valid. 

    So I think you can see where this is going.  Suppose hypothetically, user “Evil” is running our user management system.  Evil, for whatever reason, can update the DirectoryEntry property of one of our user objects.  Say that Evil does the following:

    user.DirectoryEntry = new DirectoryEntry(”IIS://importantserver/W3SVC/1/ROOT/Important/deleteApplication”);

    Note that I'm not familiar enough with the IIS provider to actually know what the IIS path would be to perform this action, so play along :)

    Now, because Evil doesn't have any sort of permissions to administer http://importantserver , this property assignment may fail or it may not - it depends on the ADSI provider, frankly.

    If the property assignment silently fails, then we persist the above ADSI path into our database.

    Now user “Admin” comes along and runs the user management application.  If the user admin app displays the directoryentries associated with a given user, the DirectoryEntry constructor will be run on the path saved in the database.

    Admin does have permission to administrate http://importantserver.  And just the process of the application running the DirectoryEntry constructor is enough to to destroy the IIS application that Evil was targeting. 

    In reality, there was at least one thing that kept this attack from working.  When Evil tried to set the DirectoryEntryPath property, we checked the Schema type of the object indicated by the path.  There's nothing in the IIS ADSI space that looks anything like a user or group schema.  This marginal validation was just enough to keep the IIS:// path from being a valid DirectoryEntry  in the eyes of the application, so it was not persisted. 

    Secondly, it seems likely that the IIS provider, given a path with a verb, would return a failure code if the verb failed, and thus the DirectoryEntry constructor would throw an exception if its underlying COM call returned the error. 

    Finally, it's not clear that you can actually construct IIS:// paths that are verbs.

     

  • The feature I just thought of has been there all along..

    So I was thinking about something or other today and wanted to refresh my mind on the details of it.  I know that i had visited a site on exactly the topic at hand sometime recently. 

    This happens pretty often.  I think browser bookmarks are next to useless, as the web seems to 404 itself into content-expiration faster than non-refrigerated milk.  Also, at the time i'm at a site, i can't possibly be expected to judge wether or not i'll want to see this same site in the future.  If it's so darn important that i KNOW i'll want it, i'll commit the domain to memory without really trying to (i used to have wuarchive's IP address memorized back in the day.  typing out “ftp 128.252.135.4“ was much faster than trying to remember what came between “wuarchive.“ and “.edu“)

    Usually what I do is google search for the exact terms that i think i would have used last time i was looking for info on this.  Then i look for the link thats a different color in the search results. 

    Sometimes I look through my browser history, but thats too noisy to pick what i want quickly - usually doing a new google search of the entire internet is faster.

    So today i had a great idea - what if there was a way to google search constrained just to links in your browser history.  I was all set to go post this suggestion internally and see what people thought, but i figured i'd check IE just to make sure no such thing existed, as maybe a right-click option on the history pane.

    Imagine my surprise as hitting ctrl-H yielded me my usual history tab, but up at the top, a button called “search”.

    Nice.  Glad i didn't make a big fit internally about how IE ought to let you search for terms in your browser history :)

    That said, i think it'd be nice if the search UI categorized links under the same visual site heirarchy that the general history control uses.  Are you listening, IE people ? :)

    So, that's my useful it-was-always-there new feature of the day. 

    I wonder if a team of product experts across windows, outlook, SQL server, VS, etc observed my product usage habits for a week, what inefficiencies they'd notice ?  MS does studies from time to time where they put a bunch of people in a room and say “do this task” and video tape it.  This is great for understanding what things are cumbersome to use about our products.  This IE feature had been under my nose and I had finally noticed my defective usage habit and thought “i wish a product did that!”.  What other features am i missing out on?  What other things do i do foolishly just because i am not aware of the built-in better way ?

     

  • Mac OS X - when it doesn't "just work"

    My wife, mother in law, father in law, and sister in law all grew up using Macs.  I grew up using various unix machines.  I had to say that I was excited, if for no other reason than family tech support, to see that OS9 would be leaving our home and theirs and being replaced with something sensible and UNIX based.

    This weekend, that sentiment bit me, badly.

    The sister in law is going to graphic design school and naturally that requires a mac and the adobe creative suite.  She was able to rustle up a powerbook (lombard, maybe ?) G3 4 or 500mhz.  Works well enough, runs OS X and all her mandatory apps.  I was impressed at how easily she was able to use it to do various computery tasks, and watching her actually work in Illustrator, drag PDFs to a network share (the other OS X machine in the house), and print from there (network printing wasn't setup) was actually pretty impressive.

    Because my in-laws are so appreciative and generally wonderful, I don't mind doing occasional tech support for the mother and sister in law (the father in law is himself technically brilliant, but short on time).  A week ago, the sister-in-law called with something of a dilemma.

    Her powerbook had stopped booting.  It would hard lock upon bootup, on the white-apple-logo screen with the spinning cursor (i love that apple added a spinning cursor during boot.. reminds me of 20 year old Sun machines with their spinning ascii cursor).  Her dad had already given the machine a once over.. the standard apple tricks (pmu reset, pram zapping, etc) and some new special OS X ones (deleting kext cache! - do mac people actually know what that means? :)

    His conclusion - hardware issue.  Uh oh.  Sister-in-law doesn't need any money-expending situations, so a hardware issue would be bad.  Her call to the various paid-apple people all involved proposals that she give them money in order for them to do something to her computer that her father had already tried doing.

    I asked her to try a few more things over the phone.  The machine oddly enough booted and ran correctly in safe mode (hold down SHIFT during bootup).  The machine would hard lock in the same place trying to boot from CD (uh oh).

    I asked her to boot single user (hold down option-S during power-on).  This drops sister-in-law into a single-user bash prompt, running as root.  Frankly, i'm not sure this is what mac users want their debugging experience to be like.  With old macs, you just kicked them or threw them away when they stopped working.  Now that there's an actual operating system under the covers, people such as myself foolishly beleive we can revive these machines with enough messing around.

    Indeed, i told her i'd fix it for her when i saw her in person next (they're a 3hr drive from us). 

    I spent about 10 hours this weekend working over the powerbook.  I know now an exhaustive amount about the OS X boot procedure.  It's a somewhat unix style boot, with some weird apple stuff thrown in at the end.

    When you boot single user, /etc/rc.boot is executed.  All this does is really do rudimentary fsck's, and then drop you to sh.  Good.

    Multi-user boot is /etc/rc

    Both look at /etc/hostconfig

    The last line of /etc/rc is something called “SystemStarter”.  Uh oh.  Capital letters mean its not unix, its NeXT/Apple stuff.  Missing are both the SysV rcN.d/Sxxfeaturename system, as well as the “its all in a big rc” BSD style approach. 

    No, SystemStarter examines /System/Library/StartupItems and /Library/StartupItems, looking for stuff to do. 

    I modified rc so that it didn't call systemstarter, and instead gave me another sh prompt.  Running SystemStarter manually (they did provide a non-daemon mode, a verbose mode, and a non-gui mode) showed that things were generally ok (except for a problem starting CrashReporter) until you tried to boot the GUI.  SystemStarter starts LoginWindow as one of its last jobs, so it was clear that something inside SystemStarter->LoginWindow wasn't right.

    Oddly enough, the binary version of systemstarter was 177.3 on this mac.  The other mac, also running 10.3.4 with latest patches, had version 177.2  How does that happen ?  One of these machines has the wrong file version on it, clearly, but both feel they've got all the latest critical updates.  Interesting.

    I did some more reading about what safe mode did, since safe mode booted fine.  Turns out that OS X retains Apples ideas about “Extensions”.  Some extensinos are “bad”, and don't get loaded during safe boot.  We've got the analagous concept in windows.  In windows, we use the registry to define service/driver sets for the various configs.  (CurrentControlSet, etc).  In OS X, the system extensions are filesystem structure based.

    You've got /System/Library/Extensions, and extension is foo.kext.  foo.kext is a subdirectory, and in this directory you've got Info.plist, which is an XML or NeXT prop-list document talking about the extensions dependancies, and as i found out eventually, what boot modes this extension is loaded under.  Also you've got MacOS/foo, the actual binary image of the loadable kernel module.

    Now, It turns out that kext's are loaded with kextload.  Loaded kexts can be queries with kextstat.  And there's a daemon which dynamically loads kexts as needed, called kextd.

    Kextd is started in /etc/rc (the multi-user script).  If safe boot is enabled, kextd -x is called, which tells kextd (as near as i can tell ) to just pass -x to kextload.  kextload -x means “only load safe-boot or root extensions”. 

    I decied i'd try and isolate the problem to kext's.  I modifed /etc/rc so that kextd -x was called regardless of if we're safe booting or not.

    After making this change, normal multi-user boot succeeded.  The machine worked great, apart from having no sound, modem, firewire, etc :)

    Clearly, there's an extension that gets loaded outside of the safe-boot set that hangs bootup somehow.  But how to isolate it ?

    On this laptop, there were 183 extensions in /System/Library/Extensions.  100+ were required for a safe boot. 

    OS X has the notion that an extension can be required for the root filesystem, and furthermore, can be required for certain types of root filesystems (i.e. local, network, or cd).  The set of extensions actually loaded during a safe boot is any extension marked required for safe boot (in its Info.plist, in the OSBundleRequired property), and any extension which is needed for root or root-filesystem mounting. 

    It is impossible to discern this, by the way, without using csh and grep.  Eventually i constructed some output files using csh, awk, grep, vi, and comm to figure out a list of which extensions were NOT used in safe mode.

    In the old world of macs, people would drag extensions out of the extensions folder into Extensions (Disabled) and drag them back in until the box worked.  They had then identified the misbehaving extension.

    Doing the same thing with csh foreach() loops and mv is just as good. 

    I started by removing every non-required kext from the default kext directory, and then reverted the kextd -x change and did a normal multi-user boot. 

    Awesome.  machine booted, but all video is 4 shades of grey (i was pleased to see this bit of NeXTSTEP was still lurking under the covers). 

    next i started adding in groups of kexts.  I'd make a textfile saying which ones i wanted to add, then add them in a foreach loop, sync the disks, and reboot.  If it booted cleanly, i'd add another batch. 

    Working with the mac in greyscale mode kind of stinks, so i wanted to sort that out in short order.  However, i found that moving all of the ATI video kexts caused a non-boot condition.  Interesting.  I had noted a few messages coming from the ATIRage128.kext driver as it loaded on previous boots (it can load in text mode and not crash) and i also noticed that trying to unload it caused a hang.  I removed the ATIRage128.kext driver and teh box booted again, albeit with a grey screen again.  Doh.  I started trying to add more kexts and noticed a folder that wasn't a kext at all - AppleNDRV.  In this folder were ATIRuntime bundles - neat.  Adding that folder back to its original location gave me a booting mac complete with color.

    After adding back all the other kexts, it would seem that the ATIRage128.kext module was responsible for the hang.  Presumably, this also explains why the boot CD crashed, as i beleive it's a stock driver.  One also wonders how im getting color video with no kernel extension for the video hardware, but, whatever :)

    the 10.3.4 update (which was applied a day or two before the box stopped working) included massive updates for ATI and NVidia chipsets.  The checksum on the actual module matched that of another ATI equipped machine (which worked, by the way).

    So, is it a hardware problem ? I donno.  When the 128 driver loads, it prints a kmessage saying its unhappy about the firmware on the video board in the powerbook (a RageM3).  Without the kext loadable, the box works perfectly, even for doing opaque drags and work in illustrator and photoshop.  What was it doing in the first place ?

    The point of all of this is - are mac users really supposed to be able to fix this themselves ?  I lived and breathed unix all through highschool and college and it took me the better part of 10 hours to get this machine booting multi-user with working audio and so on.  Will the next system update drop a new ATIRage128.kext into the default place, perhaps causing this problem again ?

    It seems that the process of debugging boot failures ought to be a bit easier than this.  I run my windows machines with /SOS and the other nice switches that give you textual boot progress, and OS X supports “verbose boot” (hold down option-V during boot), but that was no good in this case, as the crash didn't occur until the video hardware tried to do something clever, even though merely loading the kernel module for the ATIRage128 didn't cause the crash - only starting the windowserver with this module also loaded triggered the hang.

    What are the odds that Apple's paid tech support would have resolved this without a format ? (or at all?)

     


© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker