Wednesday, 12 March 2014

Webstock 2014, was all like, weeeeeeee.

http://www.webstock.org.nz/14/

Prepare yourself for basic facts:

  • two day conference for around $1000, couple of (optional) days of workshops beforehand for additional $$ 
  • heavily design focused - this is not a technical conference. You get the good stuff like motivation, inspiration and entertainment, but you prob won't learn new skills or hear about new technology.
  • single stream, so no hard decisions for you over what to see.
  • there is free ice cream.
  • occasionally there is free beer, but it is too hard to get because the line is ridiculous. But it's craft beer anyway, so don't be too upset.


Some of the presentations I particularly liked were:

  • Tom Loosemore talking about UK govs digital plans - trashing a whole lot of existing stuff that isn't working, getting a good work culture, a team that has the power to change things, making products that people want. 
  • Dan Saffer - microinteractions. Little basic things that applications do, how they can impact the user experience & be memorable or disastrous.
  • Charlie Todd - Improv everywhere. The stuff these dudes do is pretty funny so this was very entertaining.
  • Sha Hwang - When it's out just go watch it.

There are some photos here: http://www.flickr.com/photos/webstock06/

My rating, I give this conference 5 custom fonts out of a possible 5 custom fonts.

I sure do like bullet points.

Sunday, 23 February 2014

SSIS vs Oracle - Something bad happened but no one's telling you what

Trying to run an SSIS 2012 package that accesses an Oracle database through ODP.net.  It works when running the package through Visual Studio, but fails once deployed with the error:

The type initializer for 'Oracle.DataAccess.Client.OracleConnection' threw an exception.


Ok, something bad happened. There may be an internal exception here, but I guess SSIS isn't going to tell us what it is.

This is complicated by the fact that every installer that Oracle has ever built is pretty much rubbish.

Did it install correctly?
Can't the installer tell me some sort of 'success' or 'failure' rather than just closing?
Is that an information message or an error message?
Where did all the files go?
What do I currently have installed?
Why is there no entry in the install/uninstall programs control panel?
Why is it getting so confused over x86 and x64?
What files is it actually trying to use?
Why are we still using .bat files for an install it's not 1996 anymore?
Why can't I uninstall this, the uninstaller does nothing!?

Apparently the error is sometimes related to x64 trying to access x86 and vice-versa. I had some type of ODP.net installed from months ago, but wasn't sure what version or whether it was x64 or x86. Couldn't figure out how to find that info out. I tried installing both x64 and x86 versions of ODP.net but due to the installers was unable to tell if they installed correctly and still had the above error anyway.

What did work was when executing the deployed package from the SSIS server you can tell it to use the 32-bit runtime:


So I dunno. I guess this means I have the x86 version of ODP.net installed. This is A Good Thing To Know.

Wednesday, 29 January 2014

Notes on accessing AtTask REST service for Project Status reporting

AtTask is used as our project management software. The REST service for it lives here:

https://[yourcompanynamehere].attask-ondemand.com/attask/api/

The documentation is here.

To get info on projects:

https://[yourcompanynamehere].attask-ondemand.com/attask/api/project/search?



To get info on tasks:

 https://[yourcompanynamehere].attask-ondemand.com/attask/api/task/search? will return task information.

To get all of the fields for a project you can add 'field=*' to the query string.

https://[yourcompanynamehere].attask-ondemand.com/attask/api/project/search?fields=*


To just get specific fields pass the field names as comma separated.

https://[yourcompanynamehere].attask-ondemand.com/attask/api/project/search?fields=actualCost,actualCompletionDate


To filter put the field name and the value you want:

https://[yourcompanynamehere].attask-ondemand.com/attask/api/project/search?status=CUR


From .net you can use the System.Net.Http.HttpClient object to call the above URLs. You need to tell the HttpClient object that it's cool to get back a JSON response so you need to add a handler to it, something like this:

var proxy = new WebProxy
                {
                    Address = new Uri(proxyUri),
                    Credentials = CredentialCache.DefaultNetworkCredentials
                };

var handler = new HttpClientHandler
                    {
                        PreAuthenticate = true,
                        ClientCertificateOptions = ClientCertificateOption.Automatic,
                        UseProxy = true,
                        Proxy = proxy
                    };

var client = new HttpClient(handler) {BaseAddress = new Uri(baseUrl)};

// make it so we accept json as a response
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

HttpResponseMessage response = client.GetAsync(url).Result;


Then use the System.Web.Script.Serialization.JavaScriptSerializer to map the JSON into .Net objects:

var serializer = new JavaScriptSerializer();

var projects = serializer.Deserialize<IList<Project>>(json);

Where your Project object contains all the fields you are expecting from the JSON

public class ProjectDto
{
    public string ID { get; set; }
    public string name { get; set; }
    public string objCode { get; set; }
    public string percentComplete { get; set; }
    public string plannedCompletionDate { get; set; }
    public string plannedStartDate { get; set; }
    public string priority { get; set; }
    public string projectedCompletionDate { get; set; }
    public string status { get; set; }

}


You can use the above data to make reports based on real time project status:



Sunday, 26 January 2014

A Chrome Extension to alter a pages HTML using JQuery on button click


This is a chrome extension that will add a button to the browser next to the URL bar; When the button is clicked it will search the content of the current page and make any tweets it finds on the page much better.

The code is here:
https://sites.google.com/site/kpdownloadz/downloads/tweet%20improver.zip?attredirects=0&d=1

How to use this extension: I am too cheap to pay for the developer license which lets you upload it to the Chrome Web Store so you have to download it and then load it into the browser manually.

  1. Download the above zip and unpack to your PC
  2. Open Chrome and put 'chrome://extensions/' into a browser window
  3. Make sure 'Developer mode' is ticked
  4. Press 'Load unpacked extension...' and select the folder where you unpacked the above zip
  5. A little TI icon will show up to the right of the URL bar which you can click on when visiting twitter sites



There's a bit of clowning around to get access to jquery, so this is my documentation for the process. NOTE: I was a BAD DEVELOPER and didn't read the documentation; this was all copy and paste and guess, so some of it is probably a bit wrong. Anyway, you'll end up with all of these files:



1. Start with the manifest.json. This describes the extension.


{
  "name": "Tweet Improver",
  "description": "This will fix all your dumb tweets",
  "version": "2.0",
  "permissions": [
    "activeTab"
  ],
  "background": {
    "scripts": ["jquery-2.1.0.min.js","background.js"],
    "persistent": false
  },
  "browser_action": {
    "default_title": "Improve your dumb tweets now",
"default_icon": {
      "19": "TI19.png",
      "38": "TI38.png"
    }
  },
  "icons": { "16": "TI16.png",
           "48": "TI48.png",
          "128": "TI128.png" },
  "manifest_version": 2,
  "content_scripts": [ {
    "js": [ "jquery-2.1.0.min.js", "background.js" ],
    "matches": [ "http://*/*", "https://*/*"]
  }]
}

For the background item we give it two scripts, we give it jquery first as apparently it loads them in that order.  Then we give it background.js, which is the main file being called by the browser action. These files need to be locally in your extension folder.

The browser_action bit is where we describe what our little button is going to look like.  We give it a couple of icons and some text that will show as a tool tip.

The icons bit describes the icons used for the chrome://extensions page and I think also the extensions downloads page (dunno, I never put mine up there).

The content_Scripts bit says that we want jquery injected into our background.js page, so in theory we should be able to use jquery when writing code in background.js.

2. Make a background.js


This is the js file our extension is hooked up to:

chrome.browserAction.onClicked.addListener(function(tab) {

  chrome.tabs.executeScript({
file: 'doittoit.js'
  });

});


Ok, there isn't much here. We're saying that when our browserAction icon thing is clicked we want to execute the js in the file doittoit.js.  

3. Your big mess of custom javascript to manipulate the DOM


You can have whatever you want in here - you've got access to all the jquery stuff.

  var tweetText = $(".tweet-text");

  tweetText.each(function() {
var newText = "";
var currentText = $(this).text();
if(currentText.length < 4) {
newText = "8=D";
} else {
newText = "8" + Array(currentText.length-1).join("=") + "D";
}
    $(this).text(newText);
  });

  $('.js-media-container').html('');

We do a search for any elements that have the class 'tweet-text' and then replace the text of that element to something that was better than the original. We also clear out any pictures (by setting the HTML of 'js-media-container' elements to nothing).




Wednesday, 8 January 2014

Save attachments from an Exchange mail box using the EWS Managed API

So from C# I wanted be able to save email attachments being sent to an Exchange mailbox. I bet you're all like 'good luck dude, that POP/imap/mapi stuff is terrible and really I can't believe no one has sorted out a good way to do it yet'.  Yeah well screw you, have you heard about the Exchange Web Service Managed API? It's pretty sweet. It's easy to setup, easy to use & pretty intuitive. We get to use predefined objects AND I LOVE OBJECTS. This is how to do it.

The summary version:
You get a dll off microsoft, reference it in your project, give it some authentication details. It will work out where your exchange server is (i.e. you don't need to tell it the server name/ip or manually add the EWS as a service reference to your project).  Then you get to mess around with the supplied objects from that dll to pull and push data from the exchange server.  When pulling back objects from the server (e.g. an email) you often get the ability to pull back just a little bit of info (e.g. the id) or to get more details (e.g. base properties or even specific properties).  If you make a change to an object you need to push that change back to the server by calling a method on that object (e.g. message.Update(...)).

The longer version:

First you need to download the Microsoft dlls here that will give you the nifty objects and handle all the client/server communication:

http://www.microsoft.com/en-nz/download/details.aspx?id=35371

This is an MSI.  But whatevs, it puts a couple of DLLs on your system that you can then copy and reference from your project:



First you've got to create your service object.  Supply it the schema definition that you want to work with.  From what I understand this is backwards compatible; for example if you hook it up to Exchange2010 SP1, when you move to SP2 your code against the SP1 schema will still work fine, but you have the option of updating your code to run against SP2 which will have more functionality. The below code uses Microsoft Black Magic® to figure out where your exchange server is and how to talk to it's associated EWS.

static ExchangeService _service = new ExchangeService(ExchangeVersion.Exchange2010_SP2); 

static Program() 
{ 
    _service.Credentials = new WebCredentials("myusername", "password");
    _service.AutodiscoverUrl("my.email@somewhere.com"); 
} 

Next, get a reference to a specific folder in the Exchange mailbox.  The below method takes a folder name and searches in the inbox for that folder. This uses the FindFolders method on the service object. To use it just give it a starting point (e.g. the inbox or the root) a search filter (an object that describes what we're looking for) and a view (an object that describes what we want returned e.g. what properties we require on the results objects).

public static Folder GetFolderByName(string folderName)
{
    FolderView view = new FolderView(10);
    view.PropertySet = new PropertySet(BasePropertySet.IdOnly); // just give us the id in the results object
    view.PropertySet.Add(FolderSchema.DisplayName); // on second thoughts, also give us the folder name
    view.Traversal = FolderTraversal.Deep; // search sub folders

    SearchFilter searchFilter = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, folderName);

    FindFoldersResults findFolderResults = _service.FindFolders(WellKnownFolderName.Inbox, searchFilter, view);
    if(findFolderResults.Count() > 1)
        throw new Exception(string.Format("Found {0} folders with title '{1}' - the name needs to be unique; don't know which one to use", findFolderResults.Count(), folderName));

    return findFolderResults.FirstOrDefault();
}



Once you've got a folder you can find the unread emails in that folder.  This runs a search similar to the above, calling the FindItems method on the service object.  Pass it the folder id you want to search in and a filter (describing what we're looking for) and a view (describing what we want returned).  Then we do some clowning around to get the message as an EmailMessage type with some specific properties loaded (.Attachments and .HasAttachments) rather than the generic Item type.


public static List<EmailMessage> GetUnreadEmailsInFolder(Folder folder)
{
    var messages = new List<EmailMessage>();

    // only get unread emails
    SearchFilter folderSearchFilter = new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, false);
    // we just need the id in our results
    var itemView = new ItemView(10) {PropertySet = new PropertySet(BasePropertySet.IdOnly)};

    FindItemsResults<Item> findResults = _service.FindItems(folder.Id, folderSearchFilter, itemView);
         
    foreach (Item item in findResults.Items.Where(i => i is EmailMessage))
    {
        EmailMessage message = EmailMessage.Bind(_service, item.Id, new PropertySet(BasePropertySet.IdOnly, ItemSchema.Attachments, ItemSchema.HasAttachments));
        messages.Add(message);
    }

    return messages;
}


Now we have a list of emails.  Loop through them, get any attachments from the Exchange server and save them to disk.  When dealing with attachments it seems like you need to explicitly call the .Load() method of the attachment to pull it from Exchange.
To set the email as having been read just update the IsRead property and then call the .Update(...) method to push the change back to Exchange.

public static void ProcessEmail(EmailMessage message)
{
    string saveDir = ConfigurationManager.AppSettings["AttachmentSaveDirectory"];
    if (message.HasAttachments)
    {
        foreach (Attachment attachment in message.Attachments.Where(a=> a is FileAttachment))
        {
            FileAttachment fileAttachment = attachment as FileAttachment;
            fileAttachment.Load(); // populate the content property of the attachment

            using (FileStream fs = new FileStream(saveDir + attachment.Name, FileMode.Create))
            {
                using (BinaryWriter w = new BinaryWriter(fs))
                {
                    w.Write(fileAttachment.Content);
                }
            }
        }
    }
    message.IsRead = true;
    message.Update(ConflictResolutionMode.AutoResolve); // push changes back to server
}



Pack it up pack it in, jobs done.

Tuesday, 17 December 2013

Visual Studio stops loading after attempting to open a solution




Ok, so, sometimes when you've been screwing around with a solution (e.g. taking a copy of an offline solution that is normally attached to TFS) and you open it you'll find that Visual Studio chokes and never finishes loading:

And you see the balloon we all love so much

I seem to encounter this the second time I open a solution, the first time it opens fine.  Visual Studio can sit in this state forever.  Or at least for so long that I can't be bothered waiting any longer. Then it needs to be killed via Task Manager.  This kind of sucks.

To fix this you can try deleting the .suo files for the solution.  These are Visual Studios solution user options files.  They are hidden files so you might need to tell explorer to show them:


Send them to the trash and next time you open the solution it might load a bit better.


Monday, 9 December 2013

HINZ 2013 Conference - a review in bullet points so I don't have to write coherently or in quantity




  • HINZ = Health Informatics New Zealand. What is an informatic? 
  • Rotorua is pretty cool.  (this is not sarcasm)
  • So many TrendCare dolls my colleagues accused me of acquiring them through illicit means
  • QualIT have really nice free pens, they have a solid, well made feel to them.  Would probably draw blood if thrown at a colleague.
  • Vendors are generally not very good at software innovation.  Wow look at the CSC & Orion booths.
  • There were a lot of managers talking to other managers about manager things. Where my dev buddies at?
  • It was jolly nice to talk to some folk from the other DHBs and to share some knowledge and maybe some code.
  • Conclusion: this is not a conference for developers, you won't learn anything practical, but the food is ok and if you want you can stare at the hot bubbling mud and imagine throwing vendor software in there.

From Wikipedia:  Health informatics is a discipline at the intersection of information science, computer science, and health care. It deals with the resources, devices, and methods required to optimize the acquisition, storage, retrieval blah blah blah no one cares