CFSharePoint: Copy Items from One Folder to Another 0 comments   |   Tags: ColdFusion 9   SharePoint   CFSharePoint   |   Sorta-Perma-Link

In a previous post, I wrote about watching out for data type mismatches when using the CFSharePoint tag.  While this is all well and good, much of my post was based on  a misconception of how an "inout parameter" works.  While I can't claim great knowledge of this, I think I have a better understanding.

From what I understand, an inout parameter expects a specifically named parameter--let's say "Result"--to be passed in.  When you pass this parameter to the web service, the value, or "out", should be the variable that you want returned.  So if I want the "results" parameter to return its value as "finalResults", you could do something like:

results="finalResults"


In the last post, I was under the impression that the initial value of "results" HAD to be a ColdFusion data type, like a structure or array.  This is not correct.  Sure, I can pass in the appropriate data type with the "results" parameter; however, whatever structure I pass in will overwrite the "out".  This is why if you run the example I provided in the last post, copying an "About.doc" file from one folder to another will really just create a new "About.doc" with the value of the binary object I passed to it--"a."  This is because by passing in the binary object on the "in", I inadvertantly overwrote what would normally be returned in the "out".

So if you really want to use SharePoint web services to copy files from one folder to another, you'll have to combine two methods: GetItem and CopyIntoItems.  Fortunately, now that I know how inout parameters work, this is cake.

Let's look at the GetItem call real quick:

<cfsharepoint    action="getItem"
        domain="localhost/mysite"
        userName="xxxx"
        password="xxx"
        name="sourceDoc"
        wsdl="http://localhost/mysite/_vti_bin/Copy.asmx?wsdl"
        params="#{                                       

 

            URL="http://localhost/mysite/Shared%20Documents/About.doc",
            fields="sourcefields",
            stream="sourcestream",
            getItemResult="getItemResult"
        }#"
/>



Nothing much to this.  However, you should note a few things.  For "fields", notice how I'm not passing a ColdFusion variable.  Rather, I'm simply naming the "out" of the fields parameter.  The same is true for "stream."  Instead of declaring that this should be equal to some ColdFusion binary object, I'm simply naming what I want the "out" of this parameter to be.

Now, if run this and dump the results for fields and stream, I'll get a collection of methods for the FieldCollection, and the binary "stream" of the source URL I provided.  Sweet!

 

Of course, I think now you're onto what comes next.  Let's plop in the example from before:

<cfscript>
    function SmartArray(lItems,delim){
    var ArrayContainer=StructNew();
    ArrayContainer.string=ListToArray(lItems,delim);
    return ArrayContainer;
    }
</cfscript>
<cfscript>
    destinationUrls = smartArray("http://localhost/mysite/Shared%20Documents/Sub-Directory/About.doc",",");
</cfscript>

<cfsharepoint    action="CopyIntoItems"
        domain="localhost/mysite"
        userName="xxxx"
        password="xxxx"
        name="destDoc"
        wsdl="http://localhost/mysite/_vti_bin/Copy.asmx?wsdl"
        params="#{
            sourceUrl="http://localhost/mysite/Shared%20Documents/About.doc",
            destinationUrls="#destinationURLs#",
            fields="#sourcefields#",
            stream="#sourcestream#",
            copyIntoItemsResult="copyIntoResult",
            results="finalResults"
        }#"
/>


If you recall the last example, I was passing empty (or mostly empty) ColdFusion variables for the fields and stream parameters.  This was a mistake.  While the fields and stream parameters on the GetItem method are inout parameters, they are NOT such for CopyIntoItems.  Rather, these are run-of-the-mill parameters that don't return an "out," so obviously they need real data.  

Fortunately, because of our success with the GetItem method, we have the right information.  Using the "outs" provided from the fields and stream parameters in our first web service invocation, we can pass these real, correctly data-typed values to the fields and stream parameters of the CopyIntoItems method.

That's it!  Now we have successfully created a mechanism for copying files in SharePoint!

 

CFSharePoint: Watch Out for Data Type Mismatches 2 comments   |   Tags: ColdFusion 9   SharePoint   CFSharePoint   |   Sorta-Perma-Link

UPDATE:

Okay, if you really want to get confused, keep reading :).  Since posting this, I've learned some important things that clarify some misconceptions mentioned in this post.  I'm leaving it here only for historical purposes.

To get better answers, check out this post.

Hey, we all learn, right? :-)

--------------------------------------------------

In my recent posts about ColdFusion/SharePoint integration, I've been dealing with pretty vanilla web service operations.  In all of the examples, I've been passing very generic data types--mostly xml and strings.  No biggie.  It's like communicating with Twitter :)  That changes right now.

Tonight, a reader e-mailed me, asking about the _vti_bin/Copy.asmx collection of web services for SharePoint.  In a nutshell, these methods allow you to copy images from document library to document library (using CopyIntoItemsLocal) or from server to server (using CopyIntoItems). Conceptually, these web services are straightforward--specify the source file, destination, and you should be done, right?  

Well, it's a little trickier than that.  In these web services, each method calls for "out" parameters to be used.  According to M, "The out  method parameter keyword on a method parameter causes a method to refer to the same variable that was passed into the method. Any changes made to the parameter in the method will be reflected in that variable when control passes back to the calling method."  

This is mostly Greek to me, but in practicality it means two things (from what I understand...):  First, to get a result for X out parameter, you have to pass in X with the method call.  Second, X has to be of the same data type.

And there's the crux of the issue.  If a funky data type is defined in the WSDL--like the destinationURL "array of strings"--you will have to send in the right kind of data type to the method to keep the web service from getting pissed off and giving you the finger.

So back to the e-mail.  The person who e-mailed me was trying to use "CopyIntoItems" to move files around document libraries, but kept encountering a data type mismatch error whenever the web service was invoked.  Being very curious, I decided to look into this a bit, and I think I've got it figured out (or at least working!).

First, let's look at the requirements for the method:


  • SourceUrl: A String that contains the absolute source URL of the document to be copied.
  • Results: An array of CopyResult objects, passed as an out parameter.
  • Fields: An array of FieldInformation objects that define and optionally assign values to one or more fields associated with the copied document.
  • Stream: An array of Bytes that contain the document to copy using base-64 encoding.
  • DestinationUrls: An array of Strings that contain one or more absolute URLs specifying the destination location or locations of the copied document.

SourceUrl is no issue, simply a string to pass.  Another vanilla data type.

Results I have still not figured out.  It turns out, however, that if you're only interested in moving a document from one place to another, it is sufficient to pass an empty string for this.

Fields require a plain-Jane ColdFusion structure to contain the FieldInformation objects--I'm not changing them, so I'm not interacting with this whatsoever.

Ok, now it gets a little hairy. 

For Stream, the method calls for an "array of Bytes."  Fortunately, the ColdFusion docs provide a handy map to show what ColdFusion data type should be used for this--binary.  

The final parameter--DestinationUrls--is by far the most curious.  This parameter calls for an "Array of Strings".  If you're like me, you'll probably look at that and immediately try to create a variety of generic CF arrays and structs to get this to work.  However, it won't.  This parameter is not looking for an array full of strings as one might imagine.  Rather, it is expecting a structural member named "String" that contains an array of URLs.  

Fortunately, people smarter than me have already taken care of this.  Using the following bit of code which I stole from an answer-to-my-prayers-blog-post, the proper structure for this parameter can be easily created.

Ok, so that's the skinny.  Here's the final code I used to get a successfully file copy from one document library to another:


    function SmartArray(lItems,delim){
        var ArrayContainer=StructNew();
        ArrayContainer.string=ListToArray(lItems,delim);
        return ArrayContainer;
    }


    destinationUrls = smartArray("http://localhost/mysite/Shared%20Documents/How%20to%20develop%20a%20Firefox%20extension.doc",",");







                domain="localhost/mysite"
                userName="xxx"
                password="xxxxx"
                name="destDoc"
                wsdl="http://localhost/mysite/_vti_bin/Copy.asmx?wsdl"
                params="#{
                            sourceUrl="http://localhost/mysite/Shared%20Documents/Sub-Directory/How%20to%20develop%20a%20Firefox%20extension.doc",
                            destinationUrls="#destinationURLs#",
                            fields="#fields#",
                            stream="#stream#",
                            copyIntoItemsResult="",
                            results=""
                        }#"
/>


As I mentioned, this is quick fix, and I still have a lot of outstanding questions about how this works, what "results" can be leveraged to do, etc.  Feel free to chime in if you have better answers or some clever solutions to make this feel a little less hacky. :)

 

CFSharePoint: Adding Groups...and Users to Them :) 17 comments   |   Tags: ColdFusion 9   SharePoint   CFSharePoint   |   Sorta-Perma-Link
Enough of lists!  The last few posts, I've been dealing with the same old boring lists, and I've grown tired of that.  So today, I want to show a quick example of how to use some of the Users and Groups web services to handle user and group management.

So let's say I have a brand new user.  They have astounding technical skills, so I decide that I want to add her to a special group of users that I'll call "Super Users".  The only issue, however, is that this group does not exist.  So to check all the items off my list and call it a day, I need to:
  • Create the "Super Users" group;
  • Add Maria to this group; and
  • Add some missing user information about Maria
The first thing we need is to create the new SharePoint group.  This is easy:

<cfsharepoint     action="addgroup" 
        domain="localhost"  
        userName="un"
        password="pwd"
        name="theresult"
        wsdl="http://localhost/_vti_bin/UserGroup.asmx?wsdl"
        params="#{
            groupName="Super Users",
            ownerIdentifier="mycomp\joel",
            ownerType="user",
            defaultUserLoginName="mycomp\joel",
            description="A group of really cool, really powerful users :)"
            }#"
/>


Regarding the info being passed, there's nothing really to speak of.  However, you should notice that the method "addgroup" is not listed in the CF9 reference as being supported by the <cfsharepoint> tag (and believe me, a quick test reveals that they mean it!).  Fortunately, this isn't a big deal.  While the method may not be directly supported by CF9, you can always pass the full path to the correct WSDL file with the "wsdl" attribute in the <cfsharepoint> tag and it will work just fine :)  (BTW, the correct WSDL file can be determined by looking at the MSDN reference for the web service method in question).

Ok, so we've created our group.  Now we need to add Maria.

<cfsharepoint     action="addusertogroup" 
        domain="localhost"  
        userName="un"
        password="pwd"
        name="theresult"
        params="#{
            groupName="Super Users",
            userName="Maria",
            userLoginName="mycomp\Maria",
            userEmail="marai@singularityconcepts.com",
            userNotes="She's pretty funny!"
            }#"
/>


Again, super simple.  In the params, you simply pass the name of the group and some basic information about the person being added.

That's about it, nothing to it.  However, let's assume for the sake of this post dragging on just a bit longer that during the process of adding Maria to the Super Users group we made a mistake--let's say we mispelled her e-mail address.  No problem, simply invoke the "UpdateUserInfo" and pass the right information:

<cfsharepoint     action="updateuserinfo" 
        domain="localhost"  
        userName="un"
        password="pwd"
        name="theresult"
        wsdl="http://localhost/_vti_bin/UserGroup.asmx?wsdl"
        params="#{
            userName="Maria",
            userLoginName="mycomp\Maria",
            userEmail="maria@singularityconcepts.com",
            userNotes="She's pretty funny!"
        }#"
/>


As with the "AddGroup" method, the "UpdateUserInfo" is not currently supported by the <cfsharepoint> tag.  However, as before, accessing the method is as easy as passing the path to the WSDL file in the "wsdl" attribute.

So these examples have been pretty simple.  However, they continue to show how easy CF9 makes interacting with SharePoint--even unsupported methods are handled with great ease.  Yep, I've very happy :)
CFSharePoint: Adding List Items with Attachments 0 comments   |   Tags: ColdFusion 9   SharePoint   CFSharepoint   |   Sorta-Perma-Link
In my last post, I showed how simple it is to post a list item to a SharePoint list from Coldfusion 9.  In this post, I want to expand a bit, showing how to add an attachment to a newly created list item.  I also thought it would be a bit more fun to use form data, rather than hardcoding everything...so here goes :)

<cfif isDefined('FORM.submit')>
    <cfsavecontent variable="xml">
        <Batch OnError='Return'>
            <cfoutput>
                <Method ID='1' Cmd='New'>
                    <Field Name='ID'>New</Field>
                    <Field Name='Title'>#FORM.title#</Field>
                    <Field Name='Favorite_x0020_Color' #FORM.color#</Field>
                </Method>
            </cfoutput>
        </Batch>
    </cfsavecontent>
    <cfset newItem = xmlParse(xml)>
    <!---Be sure to get the actual field name from the URL, not from the display in SharePoint--->
                                
    <cfsharepoint     action="updatelistitems"
                domain="domain"  
                    userName="username"
                    password="password"
                name="theresult"
                params="#{
                    listName="AAC20C17-A87D-4985-BA1D-9DD511EE9B9A",
                    updates="#newItem#"
    }#"/>
    <cfset theID = theresult.result[2].ows_ID />

    <cfset attach = filereadbinary(FORM.myfile)>
    <cfsharepoint     action="addattachment"
                domain="domain"  
                    userName="username"
                    password="password"
                params="#{
                    listName="AAC20C17-A87D-4985-BA1D-9DD511EE9B9A",
                    listItemID="#theID#",
                    fileName="#filename#",
                    attachment="#attach#"
    }#"/>
</cfif>

<cfform name="myform" method="post" enctype="multipart/form-data" target="addlistwithattachment.cfm">
    Title:    <cfinput type="text" name="title" /><br />
    Fav Color:  <cfinput type="text" name="color" /><br />
    Filename:  <cfinput type="text" name="filename" /><br />
    File:  <cfinput type="file" name="myfile" id="myfile" /><br />
    <cfinput type="submit" name="submit" value="Submit" />
</cfform>


So really, here we have another easy bit of code.  The insert method (updatelistitems) is unchanged, except that I've tied the field values to actual form data.  

Once the item is created, you can access it's SharePoint ID from the "Result" structure that SharePoint returns from the updatelistitems method.  Here, you're looking for the "ows_ID" key--this contains the index ID of the new list item.

Note:  Depending on how you've set up the Batch object, the result structure will change.  For example, with a single Method for the Batch, the returned structure looks like:

Result-->Array-->Struct-->ows_ID.  

For multiple Methods, however, the structure changes slightly:

Results-->Result-->Array-->Struct-->ows_ID

Obviously, this is not hard to check for, but just an FYI :)

Once you have this ID, you can invoke the "addattachment" method.  As with the others, it's super easy to use.  Start by converting the file to a binary object by running "filereadbinary" on the actual file.  Now that this is done, you pass the listName (from before), the listItemID (this is the index ID of the new list item), the name of the file, and the binary object that contains the file.

That's it!  With just a few lines of code, we've established a whole process for creating new list items with attachments.  Obviously, this is a very simple example--in the real world, I should do some checking to see if a file has been submitted, that the result structure is correct, and many other things...However, I think this gives a pretty good start and shows really how simple interacting with SharePoint from ColdFusion 9 actually is.  

Have fun :)
CFSharePoint: Adding List Items 0 comments   |   Tags: ColdFusion   SharePoint   CFSharePoint   |   Sorta-Perma-Link
After pretty exciting results with retrieving list data from SharePoint using ColdFusion 9, I've been quite eager to try out some of the other methods exposed by ColdFusion.  However, retrieving lists is one thing; testing out methods that could actually modify data is another entirely.  So, I broke down and spent several hours installing WSS and SQL Server 2008 on my local machine.  Interestingly enough, there are ways to get it to run on a 32-bit Vista machine, so if you're curious how, let me know ;).

Anyway, once I got SharePoint installed, I immediately created a simple list.  It's incredibly simple--it only has two fields, "Title" and "Favorite Color."  

The first method I wanted to try out was to add a list item to this list, a name and a color.  As with the list retrieval web service, the "updateListItems" method is just as simple to implement.  Here's the code I used:

<cfsavecontent variable="xml">
    <Batch OnError='Return'>
        <Method ID='1' Cmd='New'>
            <Field Name='ID'>New</Field>
            <Field Name='Title'>Monkey</Field>
            <Field Name='Favorite_x0020_Color'>Pink</Field>
        </Method>
    </Batch>
</cfsavecontent>

<cfset newItem = xmlParse(xml)>
                        
<cfsharepoint     action="updatelistitems"
            domain="localhost"  
                userName="username"
                password="password"
            name="newitems"
            params="#{
                    listName="AAC20C17-A87D-4985-BA1D-9DD511EE9B9A",
                    updates="#newItem#"
                }#"/>

<cfdump var="#newitems#" />


This is super straight-forward.  First, you create a "Batch" element.  Basically, this allows you to define any number of "methods" that you want to run in the overall "updatelistitems" call; in other words, you could loop over a bunch of new data, create a new method node for each, and insert several records in one call.  I won't explain it in too much detail here...the MSDN docs do a better job, anyway :)

Once the Batch is created, you simply call the "updatelistitems" method, passing your credentials, the list name (GUID), and the "updates" XML which contains the Batch.  You can even specify a return object ("name"), and use it to populate data for other SharePoint calls.

Simple, huh?

The one thing to watch out for here is in the "Field" blocks in the Batch XML.  When you're browsing a list's properties in SharePoint, it's important to remember that the field names you see are ONLY the display names for those fields.  In actuality, there is a REAL name that SharePoint requires for updating fields with data.  For example, in my list, the display name for the field "Favorite Color" is exactly that--"Favorite Color."  However, SharePoint's REAL name for it is "Favorite_x0020_Color", and it will only accept this value in the "Field" section.

Yep, so that's the "updatelistitems" method.  Of course, you can extend this beyond just creating new list items.  You could actually specify several create, update and delete methods in the same Batch if you wanted--I didn't, just because I wanted to keep this example simple.  So yeah, I'm starting to get some really neat ideas about what can be done by merging CF and SharePoint...hopefully, I'll have a context in which to actually do it :)
CFSharePoint...Yes! 6 comments   |   Tags: ColdFusion 9   CFSharePoint   |   Sorta-Perma-Link
The other day, I downloaded the beta for ColdFusion 9.  While waiting for the download, I was looking through some of the marketing copy that Adobe had on their site about the new features for this release.  I about fell off my chair when I saw that not only has ColdFusion 9 increased connectivity to Microsoft's SharePoint, but they have even built a tag to wrap up the functionality into one nice little package: <cfsharepoint />.

This one little tag is incredibly powerful.  It knocks out all of the painful and cumbersome web services wrappings required to deal with SharePoint's WSDLs.  Besides having over 50 built-in functions to deal with lists, document libraries, and the like, you can optionally point to other (or custom) WSDLs to access whatever <cfsharepoint> can't get at by default. 

So after reading about this, I just HAD to try it out.  Not surprisingly, ColdFusion makes a routine task like retrieving a list extremely simple.  Here's my test:

<cfset viewFields = xmlParse("<ViewFields><FieldRef Name='Title'/><FieldRef Name='ID'/></ViewFields>")>
<cfset query = xmlParse("<Query><OrderBy><FieldRef Name='ID'/></OrderBy></Query>")>
<cfset queryOptions = xmlParse("<QueryOptions></QueryOptions>")>

<cfsharepoint
    action="getlistitems"
    domain="domain.com"
    name="spVar" 
    userName="myusername"
    password="mypassword"
    params="#{
        listName="546585D2-7AC4-493F-8DC5-F089B9EEB0AD",
        viewName="A8720E14-A847-4255-8527-055D2DD97868",
        query="#query#",
        rowLimit="20",
        viewFields="#viewFields#",
        queryOptions="#queryOptions#",
        webID=""
        }#"
/>


<cfset results="#spVar.listitems.data#">
<h1>Site Punch List</h1>
<ul>
    <cfloop index="i" from="1" to="#arrayLen(results)#">
        <li><cfoutput>#results[i].ows_Title#</cfoutput></li>
    </cfloop>
</ul>




This is very straightforward.  For supported web service methods, you simple specify the method in the "action" attribute of the <cfsharepoint> tag.  In this example, I simply hard-coded my log in credentials, but the tag optionally supports the "login" attribute which can accept a pre-built domain/username/password object.  After that, you just specify the required fields for the web service method (which can be found in Microsoft's web services docs), and you're good to go.

Simple.  The only tricky bit, really, is building out the XML required by some of Microsoft's methods.  However, Microsoft's documentation of the requirements for these is pretty good, so with a bit of reading you'll have no trouble at all.

UPDATE:  The other day, someone asked a very good question about how to retrive the list/view GUID to use in the cfsharepoint tag.  I'm sure there are better ways of doing this, but here's a quick and dirty way to do it.  Hope this helps!