Monthly Archives: November 2011

The Trouble with Format-* Commands in PowerShell


There are a handful of Format commands that can really help make your data easier to look at.

  • Format-List (FL)
  • Format-Custom (FC)
  • Format-Table (FT)
  • Format-Wide (FW)

I’ll commonly use either Format-List or Format-Table to display the data of an object in the console to see whats in there but I often times see people put this in their scripts or even worse, in the middle of a pipe.

Side Note: Another common mistake is for people to use write-host in a script, see this.

The problem with using the Format commands is that it is for displaying info and when it does this it converts your object to a special internal type object. Lets play with an IPAddress and see what we have.


$ip = [ipaddress] '192.168.1.1'

$ip

$ip | ft

$ip | gm

$ip | ft | gm

So if you take a look at the output you’ll see a couple of things,

  1. The default view of an IP object is a List view. Some objects have default definitions for views and are stored in format files here C:\Windows\System32\WindowsPowerShell\v1.0 (NEVER EDIT THIS FILE), if you want to know more about these format files comment on this post and I’ll do a write up. If a default view isnt defined (which most arent) the rule is, more than 4 properties make it a list, 4 or less make it a table.)
  2. If we ask for it as a table it will squeeze it in, which in some cases that’s great. (Dont forget ft -auto)
  3. Running Get-Member (GM) on this we see that as it goes down the pipe its left in tact (Shows us we have an IPAddress object)
  4. The important part, if we pass an object down the pipe and then format it and then check the type, we see several items spit back, all of which are Internal.Format types.

The problem here is that what we’ve started with in the pipe (an IPAddress) has been dropped and changed for Format objects, which dont have the properties of the original object.

Basically we’ve gone from an Object to formatted text which is no fun in PowerShell.

There are some cases where you want to slim down your result set or combine things in which case the cmdlet you are looking for is Select-Object (Select) which I’ve now started to use over the format cmdlets for displaying data in the console.


$ip | select address
$ip | select address | gm

You’ll notice the object type is only slightly different. the type name has been prefixed with Selected. The thing to keep in mind is if we try to use this IPAddress in a method that expects this object in its full state, for example.

$ip = [ipaddress] "..." #use a valid IP here
$limitedIP = $ip | select addressfamily
$ip | gm
$limitedIP | gm
[net.dns]::GetHostEntry
[net.dns]::GetHostEntry($limitedIP)
[net.dns]::GetHostEntry($ip)

You’ll notice the first call caused an error saying there is no overload that has a single argument which is rather misleading because both signatures require only one argument (IPAddress or String). It should probably say invalid type or something like that.

The second call works just fine.

Now, back to the format cmdlets. Normally I would have stopped right here but the PowerShell.com Tip of the Day had a pretty neat use of Format-Table in a function


$Input | Format-Table -AutoSize | Out-File $Path -Width 10000

This is actually a pretty neat use since it can be used to generate a report. I havent played with this enough to see if there is a great advantage of this method over the Export-CSV cmdlet, but still, it’s a use of the format commands in the pipe (never say never).

I hope this has helped clear things up for you more than making it worse. I will say I think one of the primary take aways from this post is that with all power that PowerShell provides its VERY easy to get yourself in trouble.

BONUS TIP: You can create custom objects quickly with Select like so.


$customobj = "" | select fname,lname
$customobj.fname = 'Justin'
$customobj.lname = 'Rich'

Enjoy!

 

P.S. Thanks to Thomas Lee for the corrections.

Modify a SharePoint 2010 (probably 2007 as well) Wiki page with PowerShell


This turned out to be much harder than I expected, so I thought it would be worth sharing.

First, you ask, why Web Services? Simple, I wanted this script to work from anywhere. with any other methods of accessing SharePoint you need to be on the server for it to work, so Web Services it is.

First step, getting a handle on your SharePoint server. PowerShell has a cmdlet for that!

New-WebServiceProxy

$proxy = New-WebServiceProxy ‘http://localhost/_vti_bin/lists.asmx’ -UseDefaultCredential

I just used local host for testing, but it works with any server and you have to specify the site, which in this case I used the default root site. I also passed my logged in creds but you can specify creds if need be.

NOTE: I found that the URL didnt always stay as it was suppose to so verify that and update it if need be.

If you want to modify/add/delete data you’ll likely want to use the List Web Service, but remember there are others (http://msdn.microsoft.com/en-us/library/ms479390(office.12).aspx )

Now that we have this List Web Service, how do we work with it? You can poke around a bit with PowerShell


$proxy | get-member

Whole lot of things there, but since its a Web Service, we want to look at methods (list of members here)

There are a handful of Get methods, but the GetListItems seems to be what we are looking for. If we check the signature we see that a few things are required.

  • ListName
  • ViewName
  • Query
  • ViewFields
  • RowLimit
  • QueryOptions

Lot of info just to get a list item so lets start to tackle this.

ListName – Simple one, a string of the list we want to work with, in my case its “myWiki” but, as per the docs, its better to use the GUID (get in to that later).

ViewName – This is a string/GUID of the view we want to use, but its optional, so I found it was easy enough to leave blank for my needs.

Query – An XML node of CAML that defines what to return.

ViewFields – An XML node of more CAML to define what views we want to see (this one is important).

RowLimit – Pretty obvious, the default (all) is what we want.

QueryOptions – An XML node of CAML for the query options

Not too bad once we start to look at it, but whats this CAML?

CAML – Collaborative Application Markup Language, its just XML with named tags (same idea as HTML)

I dont know much about CAML, and I spent a good deal of time digging, but what I found is that there is a tool for it!

http://www.u2u.be/Tools/wincamlquerybuilder/CamlQueryBuilder.aspx

This tool was a HUGE help. I’m not going to go in to details because it was fairly easy to use, and when you see the CAML I provide it will make more sense.

Now that we have a method and a way to build the queries, lets get off and running.

Using the U2U Caml builder (tweaking it a bit) I came up with this to get back the one record I’d like to modify.


<mylistitemrequest>
<Query>
<Where>
<Eq>
<FieldRef ID="ID" />
<Value Type="Counter">5</Value>
</Eq>
</Where>
</Query>
<ViewFields>
<FieldRef Name='WikiField'/>
</ViewFields>
<QueryOptions>
<IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns>
</QueryOptions>
</mylistitemrequest>

You’ll notice there are a few key words there that stick out, Query, ViewFields and QueryOptions which nicely match up with our method arguments!


$str = @'
<mylistitemrequest><Query><Where><Eq><FieldRef ID="ID" />
<Value Type="Counter">5</Value></Eq></Where>
</Query><ViewFields><FieldRef Name='WikiField'/></ViewFields><QueryOptions>
<IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns></QueryOptions></mylistitemrequest>
'@
($xml = [xml]"").LoadXml($str)

$query = $xml.SelectSingleNode('//Query')
$view = $xml.SelectSingleNode('//ViewFields')
$options = $xml.SelectSingleNode('//QueryOptions')

$wiki = $proxy.GetListItems("myWiki","",$query,$view,"",$options,"")

So we took that XML document object and ripped out the nodes and feed the nodes in to the method and stored its results in to $wiki.

Running Get-Member against our $wiki will show us that we have an XML document that we can dig in to.

$wiki.data will show us that there is one item in there, just what we were looking for.

$wiki.data.row will show us the fields of a wiki, the one we really care about is the content of that wiki which is ows_wikifield

$wiki.data.row.ows_wikifield

Some pretty basic HTML in there, in my case I get back this


<div><table id="layoutsTable" style="width:100%"><tbody><tr style="vertical-align:top"><t
d style="width:100%"><div style="width:100%"><div><p>here</p>
<p>is some</p>
<p>sample data</p>
<p>i want to add</p>
<p>after this</p></div></div></td></tr></tbody></table>
<span id="layoutsData" style="display:none">false,false,1</span></div>

Now lets tack on some text to the end of our wiki


$content = $wiki.data.row.ows_WikiField
$content = $content -replace "<p>after this</p>", "<p>after this</p><p>yay more data</p>"

We’ve updated the text, but now we have to inject that back in to our Wiki, which involves a new method and more CAML. I’m not going to bother explaining it because if you’ve followed whats above, the update will make sense.


$updatestr = @"
<Batch OnError="Continue">
<Method ID='1' Cmd='Update'>
<Field Name='ID'>3</Field>
<Field Name='WikiField'>
test
</Field></Method></Batch>
"@

($xml= [xml]"").loadXml($updatestr)
$xml.batch.Method.Field[1]."#text" = $content

$proxy.UpdateListItems("myWiki",$xml)

There is one part that should be making you scratch your head, and let me tell you, it took a while for me to figure out.

Why did i put “test” in the XML wikifield?

well, come to find out, if you dump HTML in there, it looks an aweful lot like XML. If you dont put anything then the #text field isnt generated, so i put “test” in there and then used the XML members to inject the actual content I wanted updated.

Was a bit of a trick figuring this out, so I hope it helps!