Wednesday, August 17, 2011

Powershell script to list the integer value of each member of an Enumeration

I couldn't easily find something that did this, so here is what I came up with.


[Reflection.Assembly]::LoadFile("C:\temp\Microsoft.SharePoint.dll")

foreach ($value in [Microsoft.SharePoint.SPFieldType]::GetValues
([Microsoft.SharePoint.SPFieldType]))
{
$int = [Convert]::ToInt32($value)
Write-Host $value = $int
}

Wednesday, August 3, 2011

Using PowerShell to do a find and replace on all files and subfolders

I looked around for a while and just couldn't find a script that did this for me. So here it is so I don't lose it. It was useful as part of setting up a new VS2010 project that was a complete copy of the one from another project.


$search = "SearchString"
$replace = "ReplaceString"

function RenameFile ($fileInfo)
{
if ($fileInfo.Name.Contains($search))
{
$newFullName = $fileInfo.DirectoryName + "\" + $fileInfo.Name.Replace($search, $replace)
Move-Item $fileInfo.FullName $newFullName

return 1
}
return 0
}

function RenameDirectory ($dirInfo)
{
if ($dirInfo.Name.Contains($search))
{
$newFullName = $dirInfo.Parent.FullName + "\" + $dirInfo.Name.Replace($search, $replace)
Move-Item $dirInfo.FullName $newFullName

return 1
}
return 0
}

$files = Get-ChildItem -Path . * -Recurse -Force

$i = 0
$j = 0

# Rename files
foreach ($file in $files)
{
if ($file.GetType().ToString() -eq "System.IO.FileInfo")
{
if (RenameFile($file) -eq 1)
{
$i = $i + 1
}
}
}

# Now rename directories
foreach ($file in $files)
{
if ($file.GetType().ToString() -eq "System.IO.DirectoryInfo")
{
if (RenameDirectory($file) -eq 1)
{
$j = $j + 1
}
}
}

Write-Host Renamed $i files, $j directories.

Tuesday, August 2, 2011

Musings on using Unity Interception to achieve automatic INotifyPropertyChanged

I was just about to look at using Unity to implement automatic INotifyPropertyChanged. I've been meaning to do it ever since I heard the latest version of Unity for Silverlight supported method interception. It has just dawned on me however, that I might want to look again for a better option because (and I could be wrong) but using method interception will cause me one nasty little problem.

So, Unity will do a good job with something like this:


var x = container.Resolve<IMyInterface>();
x.PropertyOne = "This will work";



But what about this code?


public void SomeMethod()
{
this.PropertyOne = "I don't think this will work";
}



What am I going on about here? In a nutshell, the change notification won't happen when setting property values from within the object itself, since internally it isn't going through Unity.

I've only thought about it for a few minutes... But I think the workaround for this won't be terribly nice... and I've just seen a posting about this project, so I think I'll go check it out first.

Tuesday, July 19, 2011

Using RIA Services Contrib's EntityGraph to easily do partial saves to entities

UPDATE (29 Nov 2011): The RIA Services contrib project now has built in support for this functionality, see here.

I love RIA Services. Using it for our latest project saved us a stack of time, however... An area I found particularly painful was doing partial saves, particularly when you may have a group of related entites that you wish to save, without having to do a full save via RIA's SubmitChanges() method. The problem with SubmitChanges() is that it saves everything, which often is not what you want.

The system we have just finished and released to production makes extensive use of RIA Services and Prism. It allows the user to have multiple edits all happening at once on the one tree of hierarchical information, with a rich UI that updates all open windows instantly when edits occur. It also has full support for concurrent users all working on the one tree of records at the same time.

Thanks to the post here, the SubmitPartialChanges method allowed us to initially do saves of single modified Entites without inadvertenly saving all other modified entities the user had open. However, as the project continued, the need arose for not only a single Entity to be saved, but related modified Entities too, all in the one save operation.

To cut a long story short, at the time, it took a lot of code to make doing this perform well, and be transactional. Making multiple calls to SubmitPartialChanges() (one for each modified Entity) is slow, puts a lot of load on the server, and is not transactional. Writing the code to do it yourself is time consuming and difficult to read. Towards the end of the project, I found the recent work done in RIA Services Contrib on Codeplex that introduces the concept of the Entity Graph.

Take a moment to check out what it does. It was too late for us (it is still in Beta at the time of this writing, and didn't exist at the beginning of our project). Recently I've taken a look at using EntityGraph to allow for easily saving the Entities in the graph.

Below is a code snippet that uses some new extension methods to easily perform partial saves. The code is quite concise, and much better than what it would have looked like otherwise. The two extension methods of note are CloneEntityGraphAndAttach() and ApplyEntityGraphState().


...

EntityGraph graph = MechanicModel.EntityGraph(tbl_Mechanic.RepairJobPartShape);

if (MechanicModel.EntityState == EntityState.New ||
MechanicModel.EntityState == EntityState.Detached)
{
addingNewMechanic = true;

// Add the new Mechanic to the temporary domain context
tempDomainContext.tbl_Mechanics.Add(MechanicModel);

// Create the repairJob-mechanic link
repairJobMechanicLink = new tbl_RepairJob_Mechanic();
repairJobMechanicLink.RepairJobId = repairJob.Id;
repairJobMechanicLink.tbl_Mechanic = MechanicModel;
tempDomainContext.tbl_RepairJob_Mechanics.Add(repairJobMechanicLink);

// Create the links to each part of the repairJob
foreach (tbl_Part part in repairJob.tbl_Part)
{
tbl_Part_Mechanic_RepairJob partLink =
new tbl_Part_Mechanic_RepairJob();
partLink.PartId = part.Id;
partLink.tbl_RepairJob_Mechanic = repairJobMechanicLink;
tempDomainContext.tbl_Part_Mechanic_RepairJobs.Add(partLink);
}
}
else
{
// Clone the object graph into the temporary context
tempDomainContext.CloneEntityGraphAndAttach(graph);
}

// Update the temporary context with the server
tempDomainContext.SubmitChanges(
submitOperation =>
{
if (submitOperation.HasError)
{
...
}
else
{
if (addingNewMechanic)
{
// Clone and then attach the new mechanic's object graph to the
// real domain context
DomainContext.tbl_Mechanics.Attach(graph.Clone());
}
else
{
// Apply the saved state back to the real entities...
DomainContext.ApplyEntityGraphState
(tempDomainContext.tbl_Mechanics.First().EntityGraph
(tbl_Mechanic.RepairJobPartShape));
}

...
}
}

...



And below are the extension methods used.


using System.ServiceModel.DomainServices.Client;
using System.Collections;
using System.Reflection;
using System;
using System.ComponentModel.DataAnnotations;
using RiaServicesContrib.DomainServices.Client;
using RiaServicesContrib.Extensions;

namespace Infrastructure
{
public static partial class DomainContextExtensions
{
/// <summary>
/// Applies the state of each Entity in the graph to the
/// clones in this DomainContext.
/// </summary>
/// <param name="graph"></param>
/// <param name="contextToApplyState"></param>
/// <remarks>This only currently works for Entities with an
/// integer Key value</remarks>
public static void ApplyEntityGraphState
(
this DomainContext contextToApplyState,
EntityGraph graph
)
{
foreach (Entity entity in graph)
{
// Apply the state of the cloned entity to the real one....
ApplyStateToMatchingEntity(contextToApplyState, entity);
}

}

/// <summary>
/// Clones the given Entity and attaches it to this DomainContext.
/// </summary>
/// <param name="contextToAttach"></param>
/// <param name="graph"></param>
/// <remarks>This only currently works for Entities with an integer
/// Key value</remarks>
public static void CloneEntityGraphAndAttach
(
this DomainContext contextToAttach,
EntityGraph graph
)
{
Entity clone = graph.Clone();

// Attach a clone of the entity graph to the temporary domain context
contextToAttach.EntityContainer.GetEntitySet(clone.GetType())
.Attach(clone);

// Apply the state of each entity to the cloned entity
foreach (Entity realEntity in graph)
{
ApplyStateToMatchingEntity(contextToAttach, realEntity);
}
}

/// <summary>
/// Applies the state of the given entity to the matching clone
/// in this DomainContext.
/// </summary>
/// <param name="domainContextToSearch"></param>
/// <param name="realEntity"></param>
/// <remarks>This only currently works for Entities with an
/// integer Key value</remarks>
public static void ApplyStateToMatchingEntity
(
this DomainContext domainContextToSearch,
Entity realEntity
)
{
FindMatchingEntity(domainContextToSearch, realEntity)
.ApplyState(realEntity.ExtractState
(RiaServicesContrib.ExtractType.OriginalState),
realEntity.ExtractState
(RiaServicesContrib.ExtractType.ModifiedState));
}

/// <summary>
/// Finds an entity with the same Key as the given entity
/// </summary>
/// <param name="realEntity"></param>
/// <param name="domainContextToSearch"></param>
/// <returns>The matching Entity, or null if not found</returns>
/// <remarks>This only currently works for Entities with an
/// integer Key value</remarks>
public static Entity FindMatchingEntity
(
this DomainContext domainContextToSearch,
Entity realEntity
)
{
// Find the entity set holding the clone
EntitySet entitySet = domainContextToSearch.EntityContainer
.GetEntitySet(realEntity.GetType());

// Find the clone within this entity set...
IEnumerator enumerator = entitySet.GetEnumerator();

// Find the Key property for the Entity
PropertyInfo keyProperty = null;
PropertyInfo[] properties = entitySet.EntityType.GetProperties();
foreach (PropertyInfo propertyInfo in properties)
{
foreach (Attribute attribute in entitySet.EntityType
.GetProperty(propertyInfo.Name).GetCustomAttributes(false))
{
if (attribute.GetType() == typeof(KeyAttribute))
{
keyProperty = propertyInfo;
break;
}
}
}

// Use the key property to find the clone with the same Id
while (enumerator.MoveNext())
{
Entity candidate = enumerator.Current as Entity;

if ((int)keyProperty.GetValue(candidate, null) ==
(int)keyProperty.GetValue(realEntity, null))
{
return candidate;
}
}

return null;
}
}
}

Sunday, November 21, 2010

Using DocX to insert text into an existing document.

Maybe I'm just slow, but I found this hard to do. Here is a simple bit of code that finally did it. It seemed to me that I should have been able to use some simpler, more obvious methods to achieve this, but they just didn't work for me.


private void button1_Click(object sender, RoutedEventArgs e)
{
using (DocX doc = DocX.Load(@"..\..\Templates\source.docx"))
{

int index = FindParagraphIndexContaining("@Marker@", doc);

Paragraph p = doc.Paragraphs[index];

Paragraph p2 = p.InsertParagraphAfterSelf("");
p2.Append("Here is a new paragraph.").Font(new System.Drawing.FontFamily("Verdana")).FontSize(11);
p2.IndentationBefore = 1.0f;

p.Remove(false);

doc.SaveAs(@"..\..\Templates\saved.docx");

MessageBox.Show("Done!");
}
}

private int FindParagraphIndexContaining(string text, DocX doc)
{
int i;

for (i = 0; i < doc.Paragraphs.Count; i++)
{
if (doc.Paragraphs[i].FindAll(text).Count > 0)
{
return i;
}
}
return -1;
}

Thursday, June 3, 2010

MSI Deployment of a Trusted Silverlight 4 Out-of-Browser Application

For our corporate network, we want to be able to deploy elevated trust Silverlight 4 applications to our users via an MSI deployment. The reason being that SL4's typical approach to installing an OOB application leaves a few things to be desired. Namely:

  • In order to do automatic updates, all XAPs must be signed. In some organisations this may not be an issue, but our department doesn't like the use of PKI.

  • In order to install an application, the user must first go to a web page where it is hosted. From then on they may run it from their Start menu. We'd like the app to be installed for them straight away.

  • Many of our users do hot-desking with their computers, and since OOB apps are installed on a per-user-per-workstation basis, these users would regularly be having to install (and update!) each application on a machine they haven't used before.



So what about sllauncher.exe?

Sllauncher.exe is great because it allows an OOB application to be installed without the user first having to go to a webpage. The shortcomings of Sllauncher are:

  • Running Sllauncher only installs the application for the current user, and only on that machine. Other users of the machine will not have the application installed. If the current user goes to another machine, they will have to then install or update the app again.

  • In order to do auto-updates, PKI must still be used.

  • Auto-updating of OOB apps is a little akward, since when an update is detected, the user is forced to exit and re-run the application before the updates take effect.



An MSI based solution

Since all our other applications are installed using MSIs and group policy, we wanted an MSI packaging option that fixed all these problems. In particular, we wanted the ability to:

  • Deliver the installation automatically via group policy when the user starts up their workstation.

  • Be able to deliver updates by either updating the XAP on the web, or even from a shared file location on the LAN

  • Not have the need for the user to exit the application before updates take effect.

  • Have the icon for the application automatically installed in their start menu.

  • Be able to take advangate of elevated trust without the need for PKI.



The solution was to create a wrapper around Sllauncher that could do this for us. It is installed in the Program Files directory of a client workstation, and is called instead of Sllauncher. It takes as a parameter the URI of the XAP for the application. When it is executed it does the following:

  • Checks to see if the OOB application is installed for the current user.

  • If the application is not installed, or if it is an older version than what is on the Intranet/LAN, it is updated.

  • Runs the application.



With this done, it is a relatively simple matter of creating a deployment project that includes this wrapper. Once installed, an app is deployed by giving the user a shortcut. Updates to the application are done by placing a new version of the XAP on the Intranet/LAN, and the wrapper will automatically update the client workstation when it is next run.

The code is shown below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Configuration;
using System.Net;

namespace SlLauncher2
{
class Program
{

// The path to the Sllauncher.exe
private static readonly string _sllauncher = string.Format("\"{0}\\Microsoft Silverlight\\sllauncher.exe\"", Environment.GetEnvironmentVariable("ProgramFiles"));

static void Main(string[] args)
{
// e.g. http://localhost/ClientBin/myapp.xap OR
// e.g. file://F:/Development/SL4Com/SL4Com/Bin/Debug/SL4Com.xap
string appUrl = args[0];
Uri appUri = new Uri(appUrl);

LaunchApplication(appUri);
}

/// <summary>
/// Does the work of installing or updating the application as necessary, and then executing it.
/// </summary>
/// <param name="appUri"></param>
private static void LaunchApplication(Uri appUri)
{
string xapFilename = appUri.Segments[appUri.Segments.Length - 1];

DateTime? installedXapLastModified; // The last modifed date of the currently installed XAP (if found)
DateTime? webXapLastModified =
GetLastModified(appUri);

// Determine the App ID of the installed version (null if not found)
string appId = GetAppId(appUri, out installedXapLastModified);

// If the xap was not found or the timestamps differ....
if (appId == null || webXapLastModified > installedXapLastModified)
{
// Install the app...
Console.WriteLine("Installing from web");
string fileName = DownloadXap(appUri);
InstallApp(appUri, fileName);
File.Delete(fileName);
}
else
{
Console.WriteLine("XAP was up to date");
}

// Run the app
OpenApp(GetAppId(appUri, out installedXapLastModified));
}

/// <summary>
/// Downloads the XAP to a temporary file.
/// </summary>
/// <param name="appUrl"></param>
/// <returns>The full path to the file.</returns>
private static string DownloadXap(Uri appUri)
{
WebRequest request = WebRequest.Create(appUri);
request.Method = "GET";

WebResponse response = request.GetResponse();
Stream receiveStream = response.GetResponseStream();

string fileName = Environment.GetEnvironmentVariable("TEMP") + @"\" + Guid.NewGuid().ToString();

FileStream output = new FileStream(fileName, FileMode.CreateNew);
BinaryWriter writer = new BinaryWriter(output);
BinaryReader reader = new BinaryReader(receiveStream);

byte[] buffer = reader.ReadBytes(1024);
while (buffer.Length > 0)
{
writer.Write(buffer);
buffer = reader.ReadBytes(1024);
}

reader.Close();
receiveStream.Close();
writer.Close();
output.Close();

return fileName;
}

/// <summary>
/// Determines the last modified date of the XAP at the specified URI.
/// </summary>
/// <param name="appUri"></param>
/// <returns></returns>
private static DateTime GetLastModified(Uri appUri)
{
WebRequest request = WebRequest.Create(appUri);

request.Method = "HEAD";

WebResponse response = request.GetResponse();

DateTime lastModified;

if (request.GetType() == typeof(HttpWebRequest))
{
lastModified = DateTime.Parse(response.Headers["Last-Modified"]);
}
else
{
lastModified = (new FileInfo(appUri.LocalPath)).LastWriteTime;
}

return lastModified;
}

/// <summary>
/// Launches the specified app ID
/// </summary>
/// <param name="appId"></param>
private static void OpenApp(string appId)
{
System.Diagnostics.Process.Start(_sllauncher, appId);
}

/// <summary>
/// Installs the specified OOB app
/// </summary>
/// <param name="appUrl"></param>
/// <param name="xapFilename"></param>
private static void InstallApp(Uri appUri, string xapFilename)
{
ProcessStartInfo startInfo = new ProcessStartInfo(_sllauncher, string.Format("/install:{0} /origin:{1} /shortcut:none /overwrite", xapFilename, appUri.OriginalString));
Console.WriteLine(_sllauncher + " " + startInfo.Arguments);

startInfo.RedirectStandardOutput = true;
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;

Process process = new Process();
process.StartInfo = startInfo;
process.Start();
string result = process.StandardOutput.ReadToEnd();
}

/// <summary>
/// Searches for the app ID of the given application
/// </summary>
/// <param name="appUrl"></param>
/// <param name="lastModified">The last modified date of the XAP, if found</param>
/// <returns>Null if not found</returns>
private static string GetAppId(Uri appUri, out DateTime? lastModified)
{
// This needs to be improved as it currently needs to be different on Windows XP/7.
string dataPath = Environment.GetEnvironmentVariable("APPDATA") + @"\..\Local Settings\Application Data\Microsoft\Silverlight\OutOfBrowser";
string appId = null;

lastModified = null;

// Search each metadata file for the XAP
foreach (string directory in Directory.GetDirectories(dataPath))
{
string file = directory + @"\metadata";
if (File.Exists(file))
{
using (StreamReader reader = new StreamReader(file, Encoding.Unicode))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();

if (line.Contains("OriginalSourceUri=" + appUri.OriginalString))
{
FileInfo fileInfo = new FileInfo(file);

appId = (new FileInfo(directory)).Name;
lastModified = fileInfo.LastWriteTime;
break;
}
}
}
}
}

return appId;
}
}
}

Silverlight 4 Deployment Guide

Documentation from Microsoft:
Silverlight 4 Deployment Guide
Group Policy Settings

Group policy settings allow control of:

  • Digital Rights Management — enable or disable playback of DRM enabled content

  • Silverlight Automatic Update Mechanism — disable the automatic update mechanism which is separate from Microsoft Update

  • Silverlight Trusted Applications — allows users to install out-of-browser applications via the Install dialog

  • WebCam and Microphone — allows webpages and applications to access the microphone and webcam

  • UDP Multicast Networking — allows webpages and applications to do UDP multicast networking