tag:blogger.com,1999:blog-30516782760376071102024-03-13T19:30:05.577-07:00Ben van MolBlog on Information Worker solutions & technologies.Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comBlogger20125tag:blogger.com,1999:blog-3051678276037607110.post-14214559745857282002010-07-06T00:46:00.001-07:002010-07-06T00:46:14.108-07:00SCRUMMmmmmmm….<p>I recently started using the SCRUM methodology to help deliver a team of SharePoint developers. Since I am still quite new to SCRUM I thought a few of you might benefit from my experiences and the pitfalls along the way…</p> <h4>Stick to the plan</h4> <p>If you ever think of using SCRUM my first and most important advice would be to start out by following the rules of SCRUM to the letter. Start out by doing what has been described in many books, websites, blogs, … change the process and the way of working only after doing a few sprint runs. Not a single rule of SCRUM has been written down without any reason. So, why would you not give it a try instead of changing the rules from the start with the risk of a failing  project, de-motivated team, … (as I did, what did you expect?!)</p> <p>In my first project as a SCRUM master I saw the benefits and knew what I wanted to achieve, but due to the limited time available I started to do the work instead of my team: I started creating user stories myself, left only limited room for discussion (just the stand-ups), didn’t really include the team in defining a sprint backlog. In fact I was just playing the project leader and pretended to use SCRUM. </p> <p>Nevertheless I hoped that by using SCRUM I motivated the team, created room for changing specifications and helped to spread the knowledge about the project amongst all team members, … But what I really achieved was a dull stand-up, no motivation and a failing project. No sense of responsibility for the project whatsoever had been introduced…and it became painfully clear.</p> <p>It was by reading a book on the subject that suddenly it appeared to me what I had been doing was all wrong! And the answer to my problems had been there all the time…I just needed to follow the rules (how many times have I been that before :) )</p> <p>Yesterday I started a new sprint planning with a renewed hope of achieving the goals I had set. The product owner had a small set of things to be done (I must say that there’s still some work there..). With the team I started to go through the list and encouraged them to ask questions. It was really funny to see how the planning poker made them nervous and afraid to not make a fool out of themselves, but the result was that they were all trying to find out what a certain job was about, what effort it would take and how it needed to be done. I was really surprised that this simple trick created such an involvement and awareness about what the team members had been doing. To cut short: we as a TEAM established a first real agreed sprint backlog. The team committed to the plan and everyone seemed to enjoy the new way of working. </p> <h4></h4> <h4>One step at a time</h4> <p>Did you ever start about a million jobs at a time and never finish one of them? Well, I did…and so did my team. When we first started using a scrum board, it was filled with users stories that involve a whole set of taks that on average take about 3 mandays. The result was that everybody was doing multiple tasks at a time and never ever one was reaching the ‘done’ column. </p> <p>More importantly it was quite impossible to follow-up on these tasks…I didn’t have a clue why things were slowing down and what the status of the project was. Yesterday we started working with user stories that are made up of a bunch of task that never take longer than a few hours. This is more manageable and more fun (because we now reach that final column on the board), and it all has a fantastic side effect: all tasks can be taken up by different members of the team. What used to be the responsibility of just one person is now distributed among the team. Communication occurs more often and the frustration seems to disappear all by this one easy little rule!</p> <h4>Demo time</h4> <p>Motivation is really key to success in my current project (have I mentioned that it is in the public sector…makes things easier to understand :) ). Therefore another really dumm ass idea of me was to win time by not doing a demo on the end of the sprint. </p> <p>It is clear now that it encourages to deliver a fully working, fully tested product at the end of each sprint. It makes my team proud if they can show off the new functionality and it again raises the awareness on the complete project. Plus it gives me a great chance to thank them for their work. </p> <p>I can’t wait to see what the result of our new approach will be. I’ll keep you posted!</p> Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-41002774213771970222010-06-18T03:32:00.001-07:002010-06-18T03:35:58.489-07:00PowerShell Escape Special Characters<p>To experiment with SharePoint 2010 without the hassle of starting slow VM’s I installed SharePoint 2010 on my Windows 7 environment. To limit the performance impact on my system when I’m not working on SharePoint I ended up with these great little <a href="http://blogs.msdn.com/b/emberger/archive/2009/11/16/stop-and-go-with-sharepoint-2010-on-your-workstation.aspx" target="_blank">PowerShell scripts</a> to start/stop the server. My SQL Server instance is named SQL$SHAREPOINT so I needed to modify the script a little to change the name of the service. However, when I changed the instance name the script was not finding the service until I realized that the $ sign is a special character in PowerShell that needs to be escaped by using the backtick `. </p> <p>One more PowerShell lesson learned!</p> Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-1142788905021298022010-01-30T14:30:00.001-08:002010-01-30T14:30:53.641-08:00Calendar changes in SP2010<p>Do you know that feeling when you just got a new toy, the one you were hoping for a long time ago…and then when you start playing with it, it has all these neat little features that you didn’t even know about...I feel like a kid again ;)</p> <p>Can’t keep up the tension any longer: SharePoint 2010 has some calendar support on steroids :)</p> <p>In MOSS 2007 and Office 2007 you had this notion of overlay calendars in Outlook: view two calendars on top of each other so you are able to compare. It was a feature only available on the Outlook client.</p> <p><a href="http://lh5.ggpht.com/_4oPsz2jG3x4/S2SzCfqrd-I/AAAAAAAAAFQ/8voTqR3e38k/s1600-h/image%5B10%5D.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/_4oPsz2jG3x4/S2SzDKznp7I/AAAAAAAAAFU/OowOhPMv2sU/image_thumb%5B6%5D.png?imgmax=800" width="415" height="296" /></a> </p> <p>SharePoint 2010 now offers the same functionality in the browser. The ribbon of a calendar contains a button ‘Calendars Overlay’ which takes you to a screen where you can add up to ten calendars to the current view. Even more, it allows you to add SharePoint calendars (across site collection boundaries) and Exchange calendars using the Exchange webservices:</p> <p><a href="http://lh5.ggpht.com/_4oPsz2jG3x4/S2SzEH5IO8I/AAAAAAAAAFY/m5gEugcU9gg/s1600-h/image%5B15%5D.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/_4oPsz2jG3x4/S2SzFJdbRwI/AAAAAAAAAFc/t8ujiQnzmSg/image_thumb%5B9%5D.png?imgmax=800" width="555" height="353" /></a> </p> <p>The result is this neat overlay view:</p> <p><a href="http://lh4.ggpht.com/_4oPsz2jG3x4/S2SzGMTTCkI/AAAAAAAAAFg/9rSz7l5Xd3o/s1600-h/image%5B40%5D.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://lh6.ggpht.com/_4oPsz2jG3x4/S2SzHPqBmpI/AAAAAAAAAFk/l5jI0dhCB-k/image_thumb%5B30%5D.png?imgmax=800" width="644" height="375" /></a> </p> <p></p> <p></p> <p></p> <p></p> <p>On the left you see the calendars in the current View. In the calendar you see multiple items lined up next to each other each with their own color code. And just to point out…there is an ‘Add’ button! </p> Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-66306665861474233982010-01-29T02:59:00.001-08:002010-01-29T02:59:02.875-08:00Minimal master page SharePoint 2010<p>The minimal master page for SharePoint 2010 is available at MSDN Code. Designers get your engines ready!</p> <p><a title="http://code.msdn.microsoft.com/odcSP14StarterMaster" href="http://code.msdn.microsoft.com/odcSP14StarterMaster">http://code.msdn.microsoft.com/odcSP14StarterMaster</a></p> Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-4915997613670623932009-11-07T10:34:00.001-08:002009-11-07T10:34:23.681-08:00Add Domain account to local Administrators group<p>One lesson learned from the Ignite sessions is that in the future release of SharePoint PowerShell is the way to go for SharePoint admins. Hundreds of commands are available to do any kind of manipulation of your server farm. Looking at the “blue screen of death” has never really encouraged me to get my hands dirty. Time to be brave and follow Todd Klindt’s advice…force yourself to do it!</p> <p>I found this <a href="http://stsadm.blogspot.com/2008/03/sample-install-script.html" target="_blank">great installation script</a> written in PowerShell by Garry Lapointe to script the installation of a MOSS 2007 portal. This got me thinking: I am installing SP 2010 on my virtual machines, so why not try to do the same and create a script for 2010?</p> <p>I started out with a script to add a domain account to the local administrators group. Must say that after this work I start to like the flexibility. More to come!</p> <pre class="c#" name="code">###################################################################
# Name: ADUserToLocalGroup.ps1
# Creation Date: November 7, 2009
#
# Purpose: Add a domain user account to a local group
#
# Inputs: username: The name of the domain user to add
# domain: The domain of the user to add
# groupName: The name of the local group to add the user to
# action: add/remove
# computerName: the name of the computer to add the user to
#
# Usage: ADUserToLocalGroup.ps1 -username {username} -domain {domain} -groupName {groupname}
# -action {add/remove} [-computername {computername}]
# If no computerName is specified the local computer is used
#
# Acknowledgements: Portions of this script were originally posted on the
# following websites. A big thanks to the original authors!
#
# http://myitforum.com/cs2/blogs/yli628/archive/2007/08/30/powershell-script-to-add-remove-
# a-domain-user-to-the-local-administrators-group-on-a-remote-machine.aspx
# http://keithhill.spaces.live.com/blog/cns!5A8D2641E0963A97!676.entry
# http://www.microsoft.com/technet/scriptcenter/resources/qanda/mar08/hey0311.mspx
# http://weblogs.asp.net/adweigert/archive/2007/10/10/powershell-try-catch-finally-comes-to-life.aspx
#
##################################################################
param
(
[string]$username = $(throw "The parameter -username is required."),
[string]$domain = $(throw "The parameter -domain is required."),
[string]$groupname = $(throw "The parameter -groupname is required."),
[string]$action = $(throw "The parameter -action is required."),
[string]$computername = "localhost"
)
#Try/catch/finally function for v1 compatibility - taken from Adam Weigert's site
function Try
{
param
(
[ScriptBlock]$Command = $(throw "The parameter -Command is required."),
[ScriptBlock]$Catch = { throw $_ },
[ScriptBlock]$Finally = {}
)
& {
$local:ErrorActionPreference = "SilentlyContinue"
trap
{
trap
{
& {
trap { throw $_ }
&$Finally
}
throw $_
}
$_ | & { &$Catch }
}
&$Command
}
& {
trap { throw $_ }
&$Finally
}
}
#Set the computername
if($computerName -eq "localhost"){
$computerName = gc env:computerName
}
Try {
$computer = [ADSI]("WinNT://" + $computername + ",computer")
$Group = $computer.psbase.children.find($groupname)
$members= $Group.psbase.invoke("Members") | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
if(($action -eq "Add") -AND ($members -contains $username)) {
"The domain account specified (" + $username + ") is already a member of the local group (" + $groupname + "). No action taken."
break
} elseif (($action -eq "Remove") -and ($members -notcontains $username)){
"The domain account specified (" + $username + ") is not a member of the group (" + $groupname + "). No action taken."
break
}
if ($action.ToLower() -eq "add"){
$Group.Add("WinNT://" + $domain + "/" + $username)
"User '" + $username + "' has been succesfully added to the group '" + $groupname + "'"
} elseif ($action.ToLower() -eq "remove"){
$Group.Remove("WinNT://" + $domain + "/" + $username)
"User '" + $username + "' has been succesfully removed from the group '" + $groupname + "'"
} else {
"No or wrong action was specified, no action was taken."
}
} -Catch {
"Exception occured in ADUserToLocalGroup: " + $_.Exception.Message
"Parameters:"
" - username: " + $username
" - domain: " + $domain
" - groupName: " + $groupname
" - action: " + $action
" - computername: " + $computername
throw $_
}</pre> Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-28923281480441778802009-11-02T14:30:00.001-08:002009-11-07T02:29:34.871-08:00SharePoint 2010 Ignite - Amsterdam<span xmlns=''><p>Just came back from the SharePoint 2010 Ignite course in Amsterdam. Together with 150 SharePoint experts I had the chance to discover all the new features in the upcoming SharePoint 2010 release. It has been an interesting experience to see how the product is evolving to a much more mature application. Some quick random highlights:
</p><ul style='margin-left: 38pt'><li>The architecture has changed a lot to allow a much more scalable and manageable topology. I think the disappearance of the SSP is one of the biggest changes in the product. Instead we now get a whole list of pluggable service applications. But there are a lot of other huge changes which will make our life a lot simpler: extended logging, multiple databases instead of storing everything in the content database, restoring content by using detached databases, sandboxing, …
</li><li>A lot of the problems in SP2007 have been solved. Commonly required features have been introduced. I think of the more mature ECM features (including taxonomies, tagging, rating, publishing of content types, document ID generation, document sets, document linking, …), WCM improvements (introduction of the ribbon, introduction of wiki's), …
</li><li>The UX improvements definitely will make this product easier to use.
</li><li>One major improvement is the BCS, formerly known as the Business Data Catalog. It has never been easier to import external content into SharePoint lists.
</li><li>Search...I'm sure you'll be amazed by FAST search. Visual result sets, deep refiners, contextual search, phonetic search, lemmatization, … search as it should be.
</li><li>Social improvements: I'm not convinced by the value of the social aspects introduced in this version. However, search based on social distance will most certainly improve the results even more. Mixed feelings about this.
</li><li>Excel services and the REST services: certainly something to look in to
</li><li>Powershell administration: I forgot the number, but more cmdlets than you can ever remember exist. Nothing you can't do in PowerShell.
</li><li>…
</li></ul><p>I can go on like this for hours. Time to get my hands on this and start to play with it…got the SharePoint bug <span style='font-family:Wingdings'>J</span>
</p><p>More to come.
</p><p>
</p><p> </p></span>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-72908818145183610052009-10-19T23:46:00.000-07:002009-10-19T23:55:58.296-07:00Exciting times<p>Starting from this week everybody should prepare for a blog-tsunami because SharePoint 2010 has arrived! It looks like Xmas is a little early this year: lot's of goodies and new resources are being published, the long awaited SharePoint Conference is taking place and many enthousiastic geeks have had to wait too long to start blogging on what they discovered in the latest release of SharePoint.</p>
<p>For those of you interested, here is a list of interesting resources:</p>
<ul>
<li><a href="http://sharepoint2010.microsoft.com/Pages/default.aspx">SharePoint 2010 Website</a> - to view SharePoint 2010 in action
</li>
<li><a href="http://social.msdn.microsoft.com/Forums/en-US/sharepoint2010general/threads">SharePoint 2010 forum</a> - for SharePoint 2010 questions</li>
<li><a href="http://www.microsoft.com/presspass/presskits/sharepoint/Default.aspx">SharePoint 2010 PressPass</a> - for the SPC 2009 keynote video, a Q&A with Jeff Teper, and more</li>
<li><a href="http://msdn.microsoft.com/en-us/sharepoint/ee514561.aspx">SharePoint 2010 Developer Center</a> - for developer info</li>
<li><a href="http://www.mssharepointitpro.com">http://www.mssharepointitpro.com</a> - for IT Pro info</li>
<li><a href="http://www.microsoft.com/sharepoint">http://www.microsoft.com/sharepoint</a> - for more SharePoint information</li>
</ul>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-89422444675187687332009-10-17T05:43:00.000-07:002009-10-18T00:43:22.319-07:00Secure communication between .NET and PHP<p>
Lately I used the Rijndael algorithm to setup a single sign-on between our public facing SharePoint site (.NET) and an internet site (PHP). Rijndael is a symmetric encryption algorithm which means that the same key is used to encrypt and decrypt a message.
</p>
<p>
This algorithm is readily available in PHP as well as in .NET so in theory implementing the secure communication channel would be as simple as using the out-of-the-box functions. As it turned out...the real world proved to be a little more complex.
</p>
<p>
Both languages use different initialization settings so a simple encrypt-decrypt between both languages turns out to be not working.
</p>
<p>
First of all the Rijndael algorithm inherits from the SymmetricAlgorithm.
The classes that derive from the SymmetricAlgorithm class use a chaining mode called cipher block chaining (CBC), which requires a key and an initialization vector (IV) to perform cryptographic transformations on data. To decrypt data that was encrypted using one of the SymmetricAlgorithm classes, you must set the Key property and IV property to the same values that were used for encryption.
</p>
<p>
Secondly most plain text messages do not consist of a number of bytes that completely fill blocks. Often, there are not enough bytes to fill the last block. When this happens, a padding string is added to the text. For example, if the block length is 64 bits and the last block contains only 40 bits, 24 bits of padding are added. For the communication to work, set the PaddingMode to Zeros.
</p>
<p>
And last but not least, I used MD5 to hash the key used for the encryption. It turns out that the implementation of this algorithm is different for both languages. PHP returns a character string of 32 characters. C# returns a 16 byte binary array. To produce a php-like md5 hash I used the following code:
</p>
<pre name="code" class="c#">
public static string MD5(string password) {
byte[] textBytes = System.Text.Encoding.Default.GetBytes(password);
try {
System.Security.Cryptography.MD5CryptoServiceProvider cryptHandler;
cryptHandler = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] hash = cryptHandler.ComputeHash (textBytes);
string ret = "";
foreach (byte a in hash) {
ret += a.ToString ("x2");
}
return ret ;
}
catch {
throw;
}
</pre>
To test all this I created a little project.The project contains a .NET webservice with to methods: TestSymmetricDecryption and TestSymmetricEncryption.
When the TestSymmetricEncryption is called, it encrypts a test message and sends the result. The TestSymmetricDecryption decrypts a message send to the service and returns the decrypted result.
Secondly I've got a php page that calls both webservices and shows the result.
c#:
<pre name="code" class="c#">
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.IO;
using System.Text;
using System.Security.Cryptography;
namespace VNTG.Rijndael.POC
{
/// <summary>
/// Summary description for Rijndael
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class RijndaelPOC : System.Web.Services.WebService
{
string key = "secret"; // can be any string
string iv = "ujkrtadxcfrzpj1Bs5fpM18doZQDGYS4";
System.Security.Cryptography.Rijndael r = null;
[WebMethod]
public string TestSymmetricEncryption()
{
InitializeRijndael();
return Encrypt("This is a test");
}
[WebMethod]
public string TestSymmetricDecryption(string message)
{
InitializeRijndael();
return Decrypt(message);
}
private void InitializeRijndael()
{
byte[] keyBytes = Encoding.ASCII.GetBytes(EncodeTo64(key));
byte[] hash = MD5.Create().ComputeHash(keyBytes);
string ret = "";
foreach (byte a in hash)
{
ret += a.ToString("x2");
}
string iv64 = EncodeTo64(iv);
byte[] ivBytes = Convert.FromBase64String(iv64);
r = System.Security.Cryptography.Rijndael.Create();
r.Padding = PaddingMode.Zeros;
r.BlockSize = 256;
r.Key = Encoding.ASCII.GetBytes(ret);
r.IV = ivBytes;
}
public string Decrypt(string str)
{
byte[] encryptedBytes = Convert.FromBase64String(str);
byte[] decryptedBytes = transformBytes(
r.CreateDecryptor(), encryptedBytes);
string plaintext = Encoding.ASCII.GetString(decryptedBytes);
int idx = plaintext.IndexOf("\0");
if (idx > -1)
plaintext = plaintext.Substring(0, idx);
return plaintext;
}
public string Encrypt(string plaintext)
{
byte[] plainBytes = Encoding.ASCII.GetBytes(plaintext);
byte[] encryptedBytes = transformBytes(
r.CreateEncryptor(), plainBytes);
return Convert.ToBase64String(encryptedBytes);
}
private byte[] transformBytes(ICryptoTransform transform,
byte[] plainBytes)
{
MemoryStream memStream = new MemoryStream();
CryptoStream cryptStream =
new CryptoStream(memStream, transform,
CryptoStreamMode.Write);
cryptStream.Write(plainBytes, 0, plainBytes.Length);
cryptStream.Close();
byte[] encryptedBytes = memStream.ToArray();
memStream.Close();
return encryptedBytes;
}
private string EncodeTo64(string toEncode)
{
byte[] toEncodeAsBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode);
string returnValue = System.Convert.ToBase64String(toEncodeAsBytes);
return returnValue;
}
}
}
</pre>
PHP:
<pre name="code" class="php">
<?php
// include soap helper library
require_once('nusoap.php');
//initialize Rijndael settings
$key = 'secret';
$iv='ujkrtadxcfrzpj1Bs5fpM18doZQDGYS4'; //must be 32 chars long
function init_rijndael ($key,$iv) {
$td = mcrypt_module_open('rijndael-256', '', 'cbc', '');
if ($td !== FALSE)
{
$expected_key_size = mcrypt_enc_get_key_size($td);
$key = substr(md5(base64_encode($key)), 0, $expected_key_size);
mcrypt_generic_init($td, $key, $iv);
}
return $td;
}
//initialize proxy port
$proxyhost = isset($_POST['proxyhost']) ? $_POST['proxyhost'] : '';
$proxyport = isset($_POST['proxyport']) ? $_POST['proxyport'] : '';
$proxyusername = isset($_POST['proxyusername']) ? $_POST['proxyusername'] : '';
$proxypassword = isset($_POST['proxypassword']) ? $_POST['proxypassword'] : '';
//initialize webservice client
$client = new nusoap_client('http://localhost:1956/RijndaelPOC.asmx?WSDL', 'wsdl',
$proxyhost, $proxyport, $proxyusername, $proxypassword);
$err = $client->getError();
if ($err) {
echo '<h2>Constructor error</h2><pre>' . $err . '</pre>';
}
//PART 1: Encrypting on php side and retrieving result on .NET side
$td = init_rijndael($key,$iv);
$param = array('message' => base64_encode(mcrypt_generic($td, 'Dit is een test')));
// Call to webservice
$decryptedResult = $client->call('TestSymmetricDecryption', array('parameters' => $param));
if ($client->fault) {
echo '<h2>Error has occured.</h2>';
} else {
$err = $client->getError();
if ($err) {
echo '<h2>Error has occured.</h2>';
}
}
//PART 2: Retrieving an encrypted message on .NET side and decrypting it on php side
$td = init_rijndael($key,$iv);
$encryptedResult = $client->call('TestSymmetricEncryption');
if ($client->fault) {
echo '<h2>Error has occured.</h2>';
} else {
$err = $client->getError();
if ($err) {
echo '<h2>Error has occured.</h2>';
}
}
//decrypt encryptedResult
$encryptedResult = mdecrypt_generic($td, base64_decode($encryptedResult['TestSymmetricEncryptionResult']));
?>
<html>
<head>
<title>Rijndael POC</title>
</head>
<body>
<table>
<tr>
<td>Decrypted message on .NET: <?php print "'".$decryptedResult['TestSymmetricDecryptionResult']."'" ?></td>
</tr>
<tr>
<td>Encrypted message on php: <?php print "'".$encryptedResult."'" ?></td>
</tr>
</table>
</body>
</html>
</pre>
<p>
Couldn't have done it without these posts:
<ul>
<li><a href="http://www.spiration.co.uk/post/1203/MD5%20in%20C%23%20-%20works%20like%20php%20md5()%20example">MD5 in C#</a></li>
<li><a href="http://www.cthrall.com/blog/?p=61">Rijndael-256 (AES256) and C# and PHP</a></li>
</ul>
</p>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-44218037300230872552009-08-11T02:37:00.001-07:002009-08-11T02:52:06.249-07:00ForwardLinks, Variations & user rights<span xmlns=''><p>Recently Tom, a colleague of mine, experienced some odd behavior using the ForwardLinks property of an SPListItem. We wanted to extend the out-of-the-box publishing features with some extra functionality using the forward links property, but ended up parsing rich text fields ourselves because of the non-expected behavior.
</p><p>First of all, the ForwardLinks property should "<em>Get a collection of hyperlinks that are associated with the item; for example, the hyperlinks in a URL Field or rich text field</em>". So when we create a publishing page that contains links to some documents, the ForwardLinks property should be updated to contain these links. In the following scenario's we found that this is not the case.
</p><p><strong>Scenario 1
</strong></p><p><a href='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjh7XC0hGZ6gVZOGZBL50ZRe12x73AOmuUR_Pla8K8aLdY5XkMAzBNKAmC3b0ktSbnge5xbcd21b8sRjQJI08IghrHHFtuj8kC6Z38o2CtuuwSRKmeBPFdWwrKCCgBJ6ejvLS5PLEhsgeg/s1600-h/scenario1.jpg'><img border='0' alt='' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjh7XC0hGZ6gVZOGZBL50ZRe12x73AOmuUR_Pla8K8aLdY5XkMAzBNKAmC3b0ktSbnge5xbcd21b8sRjQJI08IghrHHFtuj8kC6Z38o2CtuuwSRKmeBPFdWwrKCCgBJ6ejvLS5PLEhsgeg/s320/scenario1.jpg'/></a>
</p><ul><li>You create a page in /enu (variation source), with a link Link1 in the Rich Text field
</li><li>You publish the page in /enu
</li><li>The timer service propagates the page to the variations, creating a draft in all the target variations
</li><li>ForwardLinks of all target variations correctly report the link Link1.
</li><li>You publish the page in the target variations
</li><li>You add another link Link2 in /enu
</li><li>When querying the ForwardLinks as the app-pool acc, this link does not show up (the red marked I. in the schema)
</li><li>You publish the page in /enu and wait for the propagation service
</li><li>ForwardLinks is properly updated in /enu
</li><li>ForwardLinks is not updated in the variation targets when querying as a sitecoll admin
</li><li>ForwardLinks is updated when querying as app-pool acc
</li><li>You check out the page in the target variations
</li><li>You edit the rich text field in the target variations
</li><li>You check in the page in the target variations
</li><li>You publish the page in the target variations
</li><li>ForwardLinks is properly updated in the target variations
</li></ul><p><strong>Scenario 2
</strong></p><p><a href='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUffV8iKv7R0RNeKhV8-k7NVcO1b1GBqM2OU79FSM3CmQSxibhLLflz_ncwl08Y781ZxUiUIeBdMINHloI1w8arRcGCmdNcducf-fx7YbsPdaU7-4W1AiVqpKACUqoXDwHq55fwUdggaQ/s1600-h/scenario2.jpg'><img border='0' alt='' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUffV8iKv7R0RNeKhV8-k7NVcO1b1GBqM2OU79FSM3CmQSxibhLLflz_ncwl08Y781ZxUiUIeBdMINHloI1w8arRcGCmdNcducf-fx7YbsPdaU7-4W1AiVqpKACUqoXDwHq55fwUdggaQ/s400/scenario2.jpg'/></a>
</p><p>This is actually a variation on scenario 1, only this time we don't modify the variation source, only the variation targets
</p><ul><li>You create a page in /enu (variation source), with a link Link1 in the Rich Text field
</li><li>You publish the page in /enu
</li><li>The timer service propagates the page to the variations, creating a draft in all the target variations
</li><li>ForwardLinks of all target variations correctly report the link Link1.
</li><li>You publish the page in a variation target
</li><li>While logged in as the site-admin, you edit a variation target and you add a new link Link2.
</li><li>You check this variation target in as a new minor version
</li><li><strong>In this case, ForwardLinks queried as the site-admin returns the proper links. However, querying it as an app-pool account results in the wrong links</strong> (also see the red marked<strong> III.</strong> in the schema)
</li><li>While logged in as the site-admin, publish the variation target
</li><li>ForwardLinks returns the correct links for both site-admin as app-pool acc
</li></ul><p>In this case, querying ForwardLinks as the app-pool account returns the exact opposite of what was originally said: it returns the value of the latest published version instead of the latest draft version.
</p><p><strong>Scenario 3<span style='font-size:12pt'>
</span>
</strong></p><p><a href='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_NK8spzxKdHZdOIzzKbE5vufREMYYCKU5H3DjeUGwFRFUQopQmY6sxxqOeNFoPMTU8kg-10tWuO4hxMsd6vo8Zz25Zu4YqiWNISwieujKRlYLCjONTmNyUf4850mIQgiBeDyQ99Z3DHg/s1600-h/scenario3.jpg'><img border='0' alt='' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_NK8spzxKdHZdOIzzKbE5vufREMYYCKU5H3DjeUGwFRFUQopQmY6sxxqOeNFoPMTU8kg-10tWuO4hxMsd6vo8Zz25Zu4YqiWNISwieujKRlYLCjONTmNyUf4850mIQgiBeDyQ99Z3DHg/s400/scenario3.jpg'/></a>
</p><p>Almost identical to scenario 2.
</p><ul><li>Execute step 1-5 of scenario2.
</li><li>This time, log in as a user who's a member of the Designers group (not a sitecoll-admin). Edit a variation target and add a link Link2 in the rich-text field.
</li><li>While logged in as a Designer user, do a check-in of the page (minor version)
</li><li>When querying ForwardLinks as the sitecoll-admin or as app-pool, you only get a reference to Link1. Only when querying the property as the Designer-account, you get a proper reference to Link1 and Link2
</li><li>Log back in as the sitecoll-admin and publish the page in the variation target-site
</li><li>ForwardLinks property properly lists Link1 and Link2 for every user.
</li></ul><p><strong>Conclusion
</strong></p><p>Querying ForwardLinks as the application-pool account does not always return the values of the latest draft version.
</p><p>Furthermore, it looks like the values are totally random. Is there some documentation that describes the logic behind this property: in which case will it return a draft-version's links, in which case will it resort to the published version?
</p><p>Fyi, we tried the following solutions to get the draft version... but they didn't work, since everyone of them conflicted with at least 1 scenario:
</p><ul><li>Query the ForwardLinks as the same user who's specified in ModifiedBy. this conflicts with our findings in scenario1, mark II. (the red rectangle)
</li></ul><p>If we're in a variation source, query ForwardLinks as the current-user, otherwise use the app-pool acc. this conflicts with scenario2, mark III and scenario3, mark IV</p></span>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-32879152694361690162009-08-06T23:13:00.000-07:002009-08-06T23:39:45.975-07:00Nintex Workflow vs. Visio 2010With the new Visio workflow creation tool in the Office clients a lot of questions popped to our minds about the need of a custom workflow creation tool as Nintex offers:
<br/><br/>
The promise is that with the new office clients you'll be able to create new workflows. This would be a fantastic extension for end-users to create their own workflows...however...there's a small catch:
<br/>
The Visio toolkit will just be extended with SharePoint workflow tasks, so you'll be able to design a workflow, but not to configure it. After you have drawn your workflow design and validated it, you can import the XOML into SharePoint Designer and start doing the actual configuration and deployment.
<br/><br/>
So will there be a need for clients to have a third party workflow tool? As far as we know now, yes.
<br/>
First of all having these workflow tools in your browser window is quite a nice feature. No clients needed. <br/>
Secondly the deployment is all browser-based, nice and easy. Whereas with the foreseen Visio extensions it would still require multiple client-side steps.
<br/><br/>
The new Visio extensions look promising, but Nintex still offers a lot of functionalities that I don't see happening with the new workflow support in Visio.
<br/><br/>
Mike Fitzmaurice (Vice President of Nintex) opened a <a href=" http://www.wictorwilen.se/Post/Creating-SharePoint-2010-workflows-with-Visio-2010.aspx">blog </a>on this topic to answer to some rumours.
<br/>
If you want to learn about the Visio workflow design capabilities, there is a very good <a href="http://www.wictorwilen.se/Post/Creating-SharePoint-2010-workflows-with-Visio-2010.aspx">post</a> by Wictor Wilén.
<br/><br/>
To be continued...Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-1454446903887734292009-07-21T00:06:00.001-07:002009-07-23T06:14:50.189-07:00Creating custom listitem menu’s (ECB)<span xmlns=''><p>This week I was working on a custom packaging solution for Look And Feel. A colleague of mine needed to add menu items to the List Item menu's of the Style library, the masterpage library and the site collection images. Our first thought was to use CustomActions in a feature to implement this, but soon we found some problems:
</p><p><strong>Register only on the specified libraries
</strong></p><p>First of all it is impossible to define a custom action only for the lists that we needed. The <strong>RegistrationID</strong> property of the <strong>CustomAction</strong> element gives some form of control, but not as specific as we needed. For more info on Custom Actions see the following articles:
</p><ul><li><a href='http://msdn.microsoft.com/en-us/library/ms460194.aspx'>Custom Action Element (MSDN)</a>
</li><li><a href='http://blog.thekid.me.uk/archive/2007/06/23/sample-editcontrolblock-customaction-for-sharepoint.aspx'>Sample CustomActions for SharePoint</a>
</li><li><a href='http://johnholliday.net/resources/customactions.html'>SharePoint Custom Action Identifiers</a>
</li></ul><p><strong>Rights parameter insufficient
</strong></p><p>Secondly we needed to only show the menu when a user was part of a certain group. CustomActions allow you to specify the rights the user must have in order to see a certain menu-item. Again this was of no value to us because there were several groups with the same rights, but we only wanted the menu-items for a certain group.
</p><p><strong>ControlAssembly not allowed on Listitem ECB
</strong></p><p>Our last attempt was to write a custom control that renders the menu-item. If we were able to create a server-side control that could check whether we were on the right list and find out about the user roles…we were off to go…but after some reading we found out that the ControlAssembly option is not available for listitem menu's (<a href='http://weblogs.asp.net/jan/archive/2008/05/08/creating-hierarchical-menus-with-a-customaction-in-sharepoint.aspx'>see Jan Tielen's blog</a>) since the menu is rendered entirely by javascript.
</p><p><strong>Solution
</strong></p><p>I started looking at the core.js (/_layouts/1033/core.js) which contains the menu code and found the functions & mechanisms to create menu items. If you click on a menu-item the menu is rendered by the <strong>CreateMenuEx</strong> function. This function basically calls several other functions which are all responsible for a subset of menu-items. One of these functions is <strong>AddDocLibMenuItems</strong>
<strong>(m,ctx)</strong>. The <strong>AddDocLibMenuItems</strong> function takes two parameters. The first parameter, called m, represents the menu object itself; the second parameter, ctx, provides HTTP context information about the web request. I didn't want to edit the core.js file since it is a system file, so I came up with the following solution:
</p><p>
</p><pre name="code" class="js">
//Store reference to original function
var original_AddDocLibMenuItems = AddDocLibMenuItems;
//Override the original function
AddDocLibMenuItems = function(m,ctx) {
strDisplayText="Test";
strAction="alert('ok')";
strImagePath="";
CAMOpt(m, strDisplayText, strAction, strImagePath);
CAMSep(m);
//Call the original function
original_AddDocLibMenuItems(m,ctx);
}
</pre><p>The code is inserted on the listview form pages of the Style library, the masterpage library and the site collection images using a content editor webpart. What it does is override the <strong>AddDocLibMenuItems</strong> function and assign the original function to a variable. This is necessary to be able to call the original function once we injected our own code. Using this mechanism there is no need to make changes to the core.js file!
</p><p>Adding a menu item to the menu requires just one function call:
</p><p>
</p><p>CAMOpt(m, strDisplayText, strAction, strImagePath);
</p><p>
</p><p>The CAMOpt function takes four parameters: the menu object to add the new item to, the display text of the menu item, the javascript action to perform when the item is clicked and a path to an image file to associate with the item. A call to the CAMSep function adds the separator bar to the menu. Both these functions are defined in the <em>menu.js</em> file on the SharePoint server.
</p><p>
</p><p>Now if you know all about the wonderful SPAPI libraries to use the SharePoint webservices in javascript, then you know you might implement whatever you want and create advanced menuitems. My complete solution:
</p><pre name="code" class="js">
<script type="text/javascript" src="/_layouts/js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/_layouts/js/SPAPI_Core.js"></script>
<script type="text/javascript" src="/_layouts/js/SPAPI_Lists.js"></script>
<script type="text/javascript" src="/_layouts/js/SPAPI_UserGroup.js"></script>
<script type="text/javascript" src="/_layouts/js/SPAPI_UserProfile.js"></script>
<script type="text/javascript">
$(document).ready(function () {
var userName = getCurrentUserName();
if (userName != null){
var accountName = getAccountName(userName);
if(accountName != null) {
if(isMemberOfGroup(accountName,"Blog - Test site Members")) {
showLink();
}
}
}
});
//Helper functions
function showLink() {
//Store reference to original function
var original_AddDocLibMenuItems = AddDocLibMenuItems;
//Override the original function
AddDocLibMenuItems = function(m,ctx) {
strDisplayText="Test";
strAction="alert('ok')";
strImagePath="";
CAMOpt(m, strDisplayText, strAction, strImagePath);
CAMSep(m);
//Call the original function
original_AddDocLibMenuItems(m,ctx);
}
}
function isMemberOfGroup(accountName,groupName) {
var usergroup = new SPAPI_UserGroup('http://moss/sites/blog');
var items = usergroup.getGroupCollectionFromUser(accountName);
if(items.status == 200)
{
if($(items.responseXML).find("Group[Name='" + groupName + "']").size() > 0)
{
return true;
}
else
{
return false;
}
} else {
return false;
}
}
function getAccountName(userName)
{
var profile = new SPAPI_UserProfile('http://moss/sites/blog')
var p = profile.getUserProfileByName(userName);
if (p.status == 200)
{
var properties = p.responseXML.getElementsByTagName('PropertyData');
var propertyValues = new Array();
for (var i=0; i < properties.length; i++)
{
var propName = properties[i].getElementsByTagName('Name')[0].childNodes[0].nodeValue;
propertyValues[propName] = properties[i].getElementsByTagName('Value');
}
return propertyValues['AccountName'][0].childNodes[0].nodeValue;
}
else
{
return null;
}
}
function getCurrentUserName()
{
var lists = new SPAPI_Lists('http://moss/sites/blog')
var items = lists.getListItems(
'User Information List',
'',
'<Query><Where><Eq><FieldRef Name="ID"/><Value Type="Counter">' + _spUserId + '</Value></Eq></Where></Query>', // query
'<ViewFields><FieldRef Name="Name"/></ViewFields>',
1, // rowLimit
'' // queryOptions
);
if (items.status == 200)
{
var rows = items.responseXML.getElementsByTagName('z:row');
if (rows.length == 1)
{
return rows[0].getAttribute('ows_Name');
}
else
{
return null;
}
}
else
{
return null;
}
}
</script></pre><p>To create an easy deployable solution you might want to opt to insert this code through a delegate control and just perform some additional checks on the location. I know this is not the most beautiful solution, but based on the design of these menus I can't think of a better way to implement this. Of course, all suggestions are more than welcome!
</p></span>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-88815644782708898972009-07-18T12:41:00.001-07:002009-07-23T06:10:10.173-07:00Cascading fields solution using jQuery – PART 1<span xmlns=''><p>Many of my clients require cascading fields in SharePoint lists. The most common solution to this problem is creating a custom field that implements the desired functionality. In this series however, I will try to create the cascading fields using a combination of the <strong>out-of-the-box SharePoint fields</strong>, the<strong> jQuery library, SPAPI</strong> and <strong>SharePoint webservices</strong>. In this first part we'll create the supporting lists and add some functionality that allows easy input for the end-user. In part 2 I'll go deeper into creating the cascading field.
</p><p><strong>1. Requirements </strong>
</p><p>Suppose our end-user wants to have the following features:
</p><ol><li>See all cities that are related to a certain country on the display form of the country
</li><li>Have a link to insert cities for a certain country
</li></ol><p><img alt='' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgx98nujA61oMVhqak1HXGouC9x1wPaq44_MWqP67NhDhO3JSUFT1bfNqX0uHzadixa0jr8G8yzXWx-EBwnlKA87dpG8dH0UJ_bBHJ7aSgAtRoxV042PCMw97J5cN_358tSbBedO2nYyK8/s320/country.JPG'/>
</p><p>The picture shows how our solution could look like. Notice the column Cities in the Display form of a Country. This additional column is inserted through our solution. It lists all cities that are related to the current country. At the bottom of the list you find a link that allows the user to add additional cities for the current country. The link takes you to the New form of the cities list. The lookup field in that list should already point to the country.
</p><p><strong>2. Lists </strong>
</p><p>For this tutorial we create two custom lists: 'Countries' and 'Cities'. The Country list only has one field of type single text named Country.
</p><p><a href='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxIE8vSsKjycXPjRcP6zMrv_mqBsFTXWObhqsYPkYm-WExyt6bsZBbYY3DV5o73rao4JqFAXhOSIe9c-XSl0egSTQDJO62EfHjfj0xELHL0F1u6Yh0tigXbyticS2EWty3S7my8lVsqSg/s1600-h/Countries.JPG'><img border='0' alt='' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxIE8vSsKjycXPjRcP6zMrv_mqBsFTXWObhqsYPkYm-WExyt6bsZBbYY3DV5o73rao4JqFAXhOSIe9c-XSl0egSTQDJO62EfHjfj0xELHL0F1u6Yh0tigXbyticS2EWty3S7my8lVsqSg/s320/Countries.JPG'/></a>
</p><p>The Cities list contains two fields:
</p><ul><li><strong>City</strong>- single line of text
</li><li><strong>Country </strong>- lookup field to Country list
</li></ul><p><a href='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNonuLu9FEhTQxwNDiG-5YUPmUmgtUWDjMYjE-o3VGN749MtGXvHQXZuTOZ7sZTyY3MPSW-LzLAP3TzlAvjW_RxuhQKBxczsMbgvtjJoGLn89Eqmv-K-CpYe2zu85DX-pq49wQhBt0mmI/s1600-h/Cities.JPG'><img border='0' alt='' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNonuLu9FEhTQxwNDiG-5YUPmUmgtUWDjMYjE-o3VGN749MtGXvHQXZuTOZ7sZTyY3MPSW-LzLAP3TzlAvjW_RxuhQKBxczsMbgvtjJoGLn89Eqmv-K-CpYe2zu85DX-pq49wQhBt0mmI/s320/Cities.JPG'/></a>
</p><p><strong>3. Libraries </strong>
</p><p>Since this tutorial is all about the usage of jQuery in combination with out-of-the-box SharePoint we'll make live a little easier and use some excellent libraries that exist for the tasks that we need to perform.
</p><p><strong>AJAX calls to SharePoint webservices</strong>:
</p><p>The list of related cities will be retrieved by an AJAX call to the SharePoint web services. Writing the SOAP envelop is somewhat of a tedious task so we'll use the great javascript library written by Darren Johnstone that simplifies the code to connect to the SharePoint webservices. More information can be found <a href='http://darrenjohnstone.net/2008/07/22/a-cross-browser-javascript-api-for-the-sharepoint-and-office-live-web-services/'>here</a>.
</p><p><strong>Populate fields on forms using jQuery: </strong>
</p><p>Not so long ago I discovered a great library (reading another excellent blog post of the Paul Grenier ' jQuery for everyone' <a href='http://www.endusersharepoint.com/blog/?s=jQuery+for+Everyone'>series</a>) that makes your SharePoint forms look at the querystring and automatically populate the corresponding fields. This is just what we need to automatically fill in the lookup field that points to the country. The library itself can be found on <a href='http://spff.codeplex.com/'>Codeplex</a>.
</p><p><strong>4. Preparing the environment </strong>
</p><p>First we need to add the jQuery library to our SharePoint pages (see <a href='http://weblogs.asp.net/jan/archive/2008/11/20/sharepoint-2007-and-jquery-1.aspx'>Jan Tielen's blog</a> if you don't know how) to be able to write some powerful javascript. I usually go for the delegate control variant because it allows to insert jQuery for a complete site by activating a feature. To include the other three libraries where needed, we'll put a Content Editor webpart on the forms page. Since you cannot edit the SharePoint forms pages in the browser, you need to apply a little trick: Open the form (for example the EditForm.aspx of the cities list) and append to the querystring PageView=Shared&ToolPaneView=2 (more info can be found <a href='http://geekswithblogs.net/ajohns/archive/2004/02/27/2430.aspx'>here</a>). You'll be able to add a Content Editor webpart and add a link to the desired library as follows:
</p><p><script type="text/javascript" src="_layouts/js/SPAPI_Lists.js"></script>
</p><p>For now just leave as is, once we setup the solution I will link the correct libraries at the correct places.
</p><p><strong>5. Creating the solution </strong>
</p><p><strong>Adding the Cities to the DisplayForm </strong>
</p><p>To add the list of cities to the displayform of a country open the DisplayForm for one country and add the PageView=Shared&ToolPaneView=2 to the querystring. This should allow you to add a content editor webpart on the form. Inside the content editor webpart we need to link to the SPAPI libraries (SPAPI_Core.js & SPAPI_Lists.js) to do the AJAX calls. I placed these libraries into the Layouts folder of the 12 hive so we insert the following links:
</p><pre name="code" class="XML">
<script type="text/javascript" src="http://moss/sites/blog/_layouts/js/SPAPI_Lists.js"></script>
<script type="text/javascript" src="http://moss/sites/blog/_layouts/js/SPAPI_Core.js"></script>
</pre><p>Since jQuery is available we're going to use this library to add an additional row to form. The HTML code for this row should be something like this:
</p>
<pre name="code" class="xml">
<tr>
<td class='ms-formlabel' nowrap='true' valign='top'>
<h3 class='ms-standardheader'>Cities</h3>
</td>
<td id='SPFieldText' class='ms-formbody' valign='top'>
[List of Related Cities]
[Link to New City form]
</td>
</tr>
</pre>
<p>So to add it to the table we insert the following code: </p>
<pre name="code" class="js">
$(".ms-formtable tbody").append("<tr><td class='ms-formlabel' nowrap='true' valign='top'><h3 class='ms-standardheader'>Cities</h3></td><td id='SPFieldText' class='ms-formbody' valign='top'> <ul id='citiesUl'></ul><a href=''>Add new city</a></td></tr>");
</pre>
<p>Now to find the cities and place them in this row, we first need to find the ID of the current Item to query the cities list. The ID is in the querystring so I insert the following code to read out that value and store it in a variable:
</p>
<pre name="code" class="js">
function getUrlParam(name){
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
if( results == null )
return "";
else
return results[1];
}
var id = getUrlParam("ID");
</pre>
<p>Now we can create a CAML query to find all cities that are related to the current country:
</p>
<pre name="code" class="XML">
<Query>
<Where>
<Eq>
<FieldRef Name="Country" LookupId="true"/>
<Value Type="Lookup">[ID]</Value>
</Eq>
</Where>
</Query>
</pre>
<p>Great, all that is left to do is write an AJAX call to the _vti_bin/Lists.asmx webservice and display those items in the form. The SPAPI library allows to query lists with the following code (more examples can be found <a href='http://darrenjohnstone.net/2008/07/22/examples-for-the-sharepoint-and-office-live-javascript-api/'>here</a>):
</p>
<pre name="code" class="js">
var lists = new SPAPI_Lists('http://moss/sites/blog');
var items = lists.getListItems(
'Cities', // listName
'', // viewname
'<Query><Where><Eq><FieldRef Name="Country" LookupId="true"/><Value Type="Lookup">' + id + '</Value></Eq></Where></Query>', // query
'<ViewFields><FieldRef Name="Title"/></ViewFields>', // viewFields
100, // rowLimit
'<QueryOptions><IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns></QueryOptions>' // queryOptions
);
if (items.status == 200) {
// do something with the response
}
else {
alert('There was an error: ' + items.statusText);
}
</pre>
<p>Now we add some processing of the XML to the code (don't you love jQuery!) and combine all together to:
</p>
<pre name="code" class="js">
<script type="text/javascript" src="http://moss/sites/blog/_layouts/js/SPAPI_Lists.js"></script>
<script type="text/javascript" src="http://moss/sites/blog/_layouts/js/SPAPI_Core.js"></script>
<script type="text/javascript">
function getUrlParam(name){
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
if( results == null )
return "";
else
return results[1];
}
$(function() {
var id = getUrlParam("ID");
var lists = new SPAPI_Lists('http://moss/sites/blog');
var items = lists.getListItems(
'Cities', // listName
'', // viewname
'<Query><Where><Eq><FieldRef Name="Country" LookupId="true"/><Value Type="Lookup">' + id + '</Value></Eq></Where></Query>', // query
'<ViewFields><FieldRef Name="Title"/></ViewFields>', // viewFields
5, // rowLimit
'<QueryOptions><IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns></QueryOptions>' // queryOptions
);
if (items.status == 200) {
var cities ='';
$(items.responseXML).find("z\\:row").each(function() {
var liHtml = "<li>" + $(this).attr("ows_Title") + "</li>";
cities += liHtml;
//$(cities).append(liHtml);
});
$(".ms-formtable tbody").append("<tr><td class='ms-formlabel' nowrap='true' valign='top'><h3 class='ms-standardheader'>Cities</h3></td><td id='SPFieldText' class='ms-formbody' valign='top'> <ul id='citiesUl'>" + cities + "</ul><a href=''>Add new city</a></td></tr>");
}
else {
alert('There was an error: ' + items.statusText);
}
});
</script>
</pre>
<p><strong>Creating a prepopulated field </strong>
</p><p>Let's start out by creating a link to the new item form of the cities list that includes a value to prepopulate a field on the form. Since we are using the SPFF library the querystring just needs to be extended with [fieldname]=[value]. In this case the fieldname is 'Country' and the value is the ID of the current country. We already have the ID of the current country in a variable so all we have to do is create a link. Replace the link in the previous code by the following:
</p>
<pre name="code" class="XML">
<a href='../Cities/NewForm.aspx?Country=" + id + "&Source=" + escape(window.location.href) + "'>Add new city</a>
</pre>
<P>So it becomes:</p>
<pre name="code" class="js">
$(".ms-formtable tbody").append("<tr><td class='ms-formlabel' nowrap='true' valign='top'><h3 class='ms-standardheader'>Cities</h3></td><td id='SPFieldText' class='ms-formbody' valign='top'> <ul id='citiesUl'>" + cities + "</ul><a href='../Cities/NewForm.aspx?Country=" + id + "&Source=" + escape(window.location.href) + "'>Add new city</a></td></tr>");
</pre>
<p>The code for the content editor webpart on the display form of the Countries list is complete. Save it and watch the magic happen!
</p><p>The last part of the solution is to include the SPFF library on the new item form of the cities list and activate the querystring processing (examples can be found <a href='http://spff.codeplex.com/'>here</a>). Again, open the new form and add PageView=Shared&ToolPaneView=2 to the querystring. Add a Content Editor webpart and add the following code:
</p>
<pre name="code" class="XML">
<script type="text/javascript" src="_layouts/js/spff.js"></script>
<script type="text/javascript">
$(function(){
$.spff({lock:true});
});
</script>
</pre><p>If everything went ok, you can now test this connection. Clicking on the link to add a city should prepopulate the lookup field:
</p><p>In the next part we'll look at how the 2 lists can be used to create cascading fields. Stay tuned!</p>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhq0cvX9mlgfqa7NcqKqwLsx4Aeqb7VXNcwUYVj-BfZAXPutVujshkI9IMHxZ_dwCOa9msNgtuqxx0_0iAui0uzhl8tqDT0HeAfVGh_b7Dl2Os1I7Sc4x6mU72kfXDZyZzJiKEvtZvjjog/s1600-h/Cities.JPG"><img style="cursor:pointer; cursor:hand;width: 320px; height: 67px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhq0cvX9mlgfqa7NcqKqwLsx4Aeqb7VXNcwUYVj-BfZAXPutVujshkI9IMHxZ_dwCOa9msNgtuqxx0_0iAui0uzhl8tqDT0HeAfVGh_b7Dl2Os1I7Sc4x6mU72kfXDZyZzJiKEvtZvjjog/s320/Cities.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5360085696240704994" /></a>
</span>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-6851667802248388932008-09-01T04:42:00.000-07:002009-07-18T03:00:48.638-07:00SharePoint WCM pages on mobile devices<div style="TEXT-ALIGN: left">An ever present possible requirement is for Web Content pages to be accessible via mobile device. SharePoint introduces some nice features for mobile devices. Each library & list can be configured to allow a mobile view. This way your mobile device can access those lists easilly. </div>
<div style="TEXT-ALIGN: justify">Secondly you can use the variation system to create a variation for a mobile view. Creating a master page designed specifically for mobile devices can make your WCM pages available. A nice overview of the possibilities is given by <a href="http://the-north.com/sharepoint/post/Exposing-your-MOSS-WCM-to-Mobile-Devices.aspx">Jamie McAllister</a>.
</div>
<div style="TEXT-ALIGN: justify">But what when you need to create a mobile view for a variated site? Furthermore, based on the experience I've got with variations I'm not that eager to start creating variations. This made me think about an other approach to mobile views.
</div>
<div style="TEXT-ALIGN: justify">Based on an <a href="http://www.sharepointblogs.com/dwise/archive/2007/01/08/one-master-to-rule-them-all-two-actually.aspx">article </a>about changing the masterpage for application pages using a HttpModule we constructed the following solutioin.
</div>
<ol><li>Extend your WCM application to an other url, specifically for use with mobile devices e.g. mobile.company.com</li><li>Create your mobile masterpage/CSS/... and store it in the original site collection
</li><li>Create an HttpModule that replaces the default masterpage with your new mobile masterpage (make sure to read this <a href="http://www.pings.dk/blog/archive/2007/11/04/change-master-page-on-the-fly-via-httpmodule-version-2.aspx">post</a> about Publishing Pages!)
</li><li>Register the HttpModule in your web.config.</li><li>Grab your device and see the magic happening!</li></ol>
<div style="TEXT-ALIGN: justify">On codeplex there is a <a href="http://www.codeplex.com/dsrm">project</a> that provides an HttpModule that can be configured in the web.config file. It might be usefull to take a look at that before writing your own HttpModule. </div>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-64313743708194871652008-09-01T03:35:00.000-07:002009-07-18T02:52:13.837-07:00SQL Server - SharePoint Configuration Wizard<div style="text-align: justify;">Wizards are nice as long as nothing goes wrong. Behind the scene the carry out a lot of work and all you have to do is click on a button, wait...and pray to every God of every world religion that all goes well! Because if it doesn't....that's where the hair pulling starts!
</div>
<span class="fullpost">
<div style="text-align: justify;">Last week I got a call from a collegue of mine to take a look at a service pack installation problem on a MOSS 2007 server. All installations were carried out successfully, but when the configuration wizard kicked in, they received an error about a <span style="font-weight: bold;">connection failure using Named instances</span>. Because we disabled named instances & only allowed connections using TCP we were supprised by the error message.
</div>
<div style="text-align: justify;">Digging deeper into the system gave some insight into how SQL Server 2005 works and what causes the error:
</div>
<div style="text-align: justify;">In the early days SQL Server only allowed one instance per server which listened to port 1433 for incomming connections. As SQL Server 2000 introduced support for multiple instances a new service (SSRP) needed to provide a way of determining which instance needed to be connected to. This service was later replaced by SQL Server Browser service. Essentially this listener service responds to client requests with the names of the installed instances, and the ports or named pipes used by the instance.
</div>
<div style="text-align: justify;">When the SharePoint Configuration Wizard kicks in, it requests information from the SQL Server Browser service about the instance to connect to. A failure contacting the listener will result in the "connection failure using named instances". Even though you disabled named instances and only allow TCP connections.
</div>
<div style="text-align: justify;">In our case the Service was running, but due to a SQL Server 2000 also running on the server it somehow got mashed up. Removing the old SQL Server 2000 instances & restarting the service solved the problem.
</div>
Lessons to be learned:
<ul><li>Make sure the SQL Server Browser service is up and running on the SQL Server</li><li>Make sure that no firewall is blocking the SQL Server communication (open up 1433 for TCP & 1434 for UDP)</li></ul><div style="text-align: justify;">If you do not want to open port 1434 or don't want to start the SQL Server Browser Service you can still run the configuration wizard by using the command line. This way you can provide all parameters manually to connect to the instance directly.
</div>
<div style="text-align: left;">Psconfig –cmd configdb –create –server "dbserver\instance,1433" –database SharePoint_Config –user adomain\username –password ***** –admincontentdatabase SharePoint_Admin
</div>
More information on SQL Server Browser Service:
<a href="http://msdn.microsoft.com/en-us/library/ms181087.aspx">http://msdn.microsoft.com/en-us/library/ms181087.aspx</a>
</span>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-81364350315711917702007-12-10T12:18:00.000-08:002009-07-18T08:15:40.558-07:00Site Template - CategoryMany times site templates are a good solution to store out-of-the-box customisations. These templates can be retrieved under the 'Custom' tab during the site creation process.<br/><br/>
You can however create your own custom tabs, be it with a little workaround: after some searching I found out that the site definition on which the site template is based, apparantly defines the name of tab! Here is how you do it...
<br/><br/>
1. Create a custom site definition to define the category. You can for example copy the Publishing template.<br/><br/>
2. In the webtemp file of you site definition provide the FilterCategories parameter on the Configuration tag. Assign it the string you want to appear on the tab:<br/><br/>
< Template Name="CUSTOMSITE" ID="1001" ><br/>
< Configuration ID="0" Title="Custom Web Site" Hidden="FALSE" <br/>ImageUrl="/_layouts/images/stsprev.png"
Description="Customized site definition for My Team Site" <br/>DisplayCategory="My Site Definitions" <strong>FilterCategories="My Custom Site Templates"</strong> > <br/>
< /Configuration ><br/>
< /Template ><br/>
<br/>
3. Install your custom site definition<br/><br/>
4. Create a site based on you site definition and customize it.<br/><br/>
5. Save your site as template<br/><br/>
Your site template will be available on the site creation page under the tab 'My custom site templates'. <br/><br/>
A site template is in fact a cab-file with a manifest.xml file in it. In this xml file the site definition ID is stored. Based on this ID the FilterCategories parameter of the site definition is retrieved.<br/><br/>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-29399704207149464242007-12-06T01:44:00.000-08:002009-07-18T08:15:58.639-07:00Lookup FieldsAnother post on relational data in SharePoint.<br/><br/>
The lookup field turns out to be one hell of a powerfull tool! Suppose you want to relate two different list items or documents. For this purpose you need to use the lookup field.<br/>
This field internally stores the ID of the related item, but in the drop-down it displays whatever field you want. However, in your CAML queries you can use both values!
<br/><br/>
To query the display-value, your CAML would look like this:
<br/>
<div style="PADDING-RIGHT: 10px; PADDING-LEFT: 10px; PADDING-BOTTOM: 10px; PADDING-TOP: 10px; BACKGROUND-COLOR: #DDDDDD;border:1px black solid">
<Query>
<br/> <Where>
<br/> <Contains>
<br/> <FieldRef Name="Event" />
<br/> <Value Type="Lookup">Cronos</Value>
<br/> </Contains>
<br/> </Where>
<br/></Query>
</div>
<br/><br/>
To perform a query on the ID you just need to specify LookupId='true' in the FieldRef tag:<br/><br/>
<div style="PADDING-RIGHT: 10px; PADDING-LEFT: 10px; PADDING-BOTTOM: 10px; PADDING-TOP: 10px; BACKGROUND-COLOR: #DDDDDD;border:1px black solid">
<code>
<Query>
<br/> <Where>
<br/> <Eq>
<br/> <FieldRef Name="Event" LookupId="true"/>
<br/> <Value Type="Lookup">7</Value>
<br/> </Eq>
<br/> </Where>
<br/></Query>
</code>
</div>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-92076328365276476522007-12-06T00:03:00.000-08:002009-07-18T08:16:13.853-07:00Relational Data in SharePointRecently I needed to develop a simple subscription system in a WSS 3.0 environment. My client wanted to have a simple event list on which employees could register (with approval).<br/><br/>
One of the fantastic 40 application templates addresses this topic, but as I started thinking about it...I wanted to try to make use of the new possibilities of wss 3.0 concerning multiple content types in a custom list. As it turns out my efforts took me quite far!<br/><br/>
Let's start by briefly listing the functional requirements:<br/><br/>
<ol>
<li> There needs to be a table/list to store all events. Several different event types exist: internal or external, workshop or course.
<li>Users can register for internal events, an approval workflow is needed.
</ol>
The obvious choice would be to use a lookup field on the subscription list to link the 2 lists. However, this would mean we have to write some eventhandlers on the first list to ensure that when an event is deleted, the related subscriptions also get deleted. Why not try to reduce the coding by using a different approach.
<br/><br/>
Since wss 3.0 it is possible to use multiple content types on one list. So all event types can be joined into one list.
To ensure that no event handlers need to be written, we'll need to combine the registration and the event list into one custom list. Again this poses no problem: folders can be used to define a relation between an event and a registration.
<br/><br/>
So the final idea would be to create some content types for the events that inherit from the folder content type. The registrations for each event are stored in a different content type inside the folder of the event.
<br/><br/>
We end up with a flexible system! As a event gets deleted, all registrations within the folder are deleted. And no coding needed!
<br/><br/>
Now we only need to define some views, an approval workflow, use some customized CQWP's to ensure a nice layout and we're all set!
<br/><br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfjZnpcqYcbeU8d_9LnpPU2e1n9JjxDgljaL-gNo_RsJvWpx0-OAYJw6XnUT6Y0p9cLOE-M8TmrpxH_SmOsSisQ3VAv1nYJCPw5v4MMnE3YIPXgi2f6jEtSggGYaAdCUrvOAJNyVr5K24/s1600-h/events.gif"><img id="BLOGGER_PHOTO_ID_5140790005750550370" style="DISPLAY: block; MARGIN: 0px auto 10px; CURSOR: hand; TEXT-ALIGN: center" height="220" alt="" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfjZnpcqYcbeU8d_9LnpPU2e1n9JjxDgljaL-gNo_RsJvWpx0-OAYJw6XnUT6Y0p9cLOE-M8TmrpxH_SmOsSisQ3VAv1nYJCPw5v4MMnE3YIPXgi2f6jEtSggGYaAdCUrvOAJNyVr5K24/s400/events.gif" width="462" border="0" /></a>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-74131756648175957832007-10-30T09:13:00.000-07:002009-07-18T08:16:29.509-07:00Debugging in SharePoint LandDebugging in SharePoint might seem at first a little bit harder to do, but <a href="http://blog.thekid.me.uk/archive/2007/07/25/debugging-tips-for-sharepoint-and-wss-exceptions.aspx">this excellent post </a>shows that you still have a lot of day-saving tools at hand!Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-36013747008642939392007-10-30T00:51:00.000-07:002007-10-30T00:57:27.326-07:00CS1010: Newline in ConstantYesterday I bumped into a strange flaw in the IIS compilation logic:
The error appeared on a ASP.NET page where I needed to include a string with some script-tags. Apparently the compiler mistook the script-tags in the string for the script tags in the ASP.NET page where I was writing the code.
Thanks to the <a href="http://weblogs.asp.net/sbehera/archive/2005/10/11/427228.aspx">this blog</a>, I figured out that misleading the compiler would do the trick:
string strAll = "<SCRIPT lanquage='JScript'>window.alert('" + strValue + "');<"+"/SCRIPT>";Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.comtag:blogger.com,1999:blog-3051678276037607110.post-1603807810041955592007-10-27T03:55:00.000-07:002007-10-28T02:35:13.837-07:00Accessing InfoPath File Attachments in code<p>Welcome to my first SharePoint-blog! I can't image life as a programmer without the millions and millions of interesting posts on the internet that helped me reach my goals time after time. Since this helped me a lot, time has come to do my part of the share...hopefully I can provide you with some interesting posts as I learn more about SharePoint! Here we go!</p>
<p>
The latest project I have been working on required some InfoPath forms processing: once a form was stored in a SharePoint library an EventReceiver needed to process the XML and send an e-mail with a summary of the InfoPath fields. Nothing special, I hear you think...indeed nothing special if it wasn't for the file attachments in the form. Somehow these needed to be converted from a meaningless string back to a file.
</p><p>
If you take a look at the XML that InfoPath produces for the XML file, you'll notice the tag contains one big base64 encoded string. But decoding the string is not enough to extract the file: upon attaching a file InfoPath builds a header which has the following structure:
</p>
<p>
<table border="1">
<tbody><tr>
<td><b>Size</b></td><td><b>Contents</b></td>
</tr>
<tr>
<td>0: 4 bytes</td>
<td>[DECIMAL]199, 73, 70, 65</td>
</tr>
<tr><td>4: DWORD (4 bytes)</td><td>Size of the header</td></tr>
<tr><td>8: DWORD (4 bytes)</td><td>InfoPath Version</td></tr>
<tr><td>12: DWORD (4 bytes)</td><td>Reserved</td></tr><tr><td>16: DWORD (4 bytes)</td><td>File Size</td></tr>
<tr><td>20: DWORD (4 bytes)</td><td>Size of FileName buffer</td></tr>
<tr><td>24: Variable</td><td>Name of the file</td></tr></tbody></table>
</p>
<p>
The first four bytes are not chosen randomly: the first byte is an unexistent ASCII code. Just to make sure the XML field is not misinterpreted as a text field. The other 3 bytes identify the field as an <strong>I</strong>nfopath <strong>F</strong>orm <strong>A</strong>ttachment (73: I, 70: F, 65: A)
</p><p>
So in order to attach the file to a e-mail I need to read out the filename size, the filename and of course the file. Some code:
<div style="white-space:pre; overflow:auto;">
// file = the deserialized version of the InfoPath Form
// objAttachmentResponse = the attachment field
byte[] xmlFileString = file.objAttachmentResponse;
if (xmlFileString != null)
{
//length of filename is on place 20 in the byte[], encoded in unicode --> x2
int nameBufferLen = xmlFileString[20] * 2;
byte[] fileNameBuffer = new byte[nameBufferLen];
//Name of the file is on place 24 in the byte[]
for (int j = 0; j < nameBufferLen; j++)
{
fileNameBuffer[j] = xmlFileString[24 + j];
}
//decode the filename
char[] asciiChars = UnicodeEncoding.Unicode.GetChars(filenameBuffer);
string fileName = new string(asciiChars);
//Remove the trailing \0 character
fileName = fileName.Substring(0, fileName.Length - 1);
//file content can be retreived after the fileName in the byte[]
byte[] fileContent = new byte[xmlFileString.Length - (24 + nameBufferLen)];
for (int j = 0; j < fileContent.Length; j++)
{
fileContent[j] = xmlFileString[24 + nameBufferLen + j];
}
//attach the file to the mail
MemoryStream ms = new MemoryStream(fileContent);
mail.Attachments.Add(new Attachment(ms, filename));
}
</div>Ben van Molhttp://www.blogger.com/profile/17131680298164733629noreply@blogger.com