Thursday, May 15, 2008

Custom Cmdlets - Part two

In this second installment, I am going to introduce a couple of Cmdlets that I didn't write for myself. I have mentioned in the past that I am on a two-year assignment in Bangkok, Thailand. In order to keep our family and friends up to speed with our exciting lives on the other side of the world, I created a blog on Blogger that my wife posts to every week.

Like the typical family blog, the posts to The Hillmans in Thailand primarily consist of that week's photos along with a description of each one. One Sunday night, I walked into the office while my wife was painstakingly uploading each photo, one by one, using Blogger's "Add Image" dialog. I asked her if she would be interested in a better way. She was hesitant, but said yes.

I wanted to create one Cmdlet that would upload pictures to Blogger and create a draft of a post that could be edited later. I used the excellent Blogger Data API to create a Blogger Cmdlet, but, unfortunately, this API doesn't have a method for uploading photos. I turned to Flickr and the FlickrNeT API to fill that need. I ended up with two new Cmdlets, Upload-Flickr and Post-Blogger.

I'll start with Upload-Flickr:
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.IO;
using System.Net;
using FlickrNet;
using System.Diagnostics;
 
namespace CustomCmdlets
{
    [Cmdlet( "Upload", "Flickr", SupportsShouldProcess = true )]
    public class UploadFlickr : PSCmdlet
    {
        private List<FileInfo> imageList;
 
        #region Parameters
 
        private FileInfo[] images = new FileInfo[ 0 ];
 
        [Parameter( ValueFromPipeline = true )]
        [ValidateNotNullOrEmpty]
        public FileInfo[] Images
        {
            get
            {
                return images;
            }
            set
            {
                images = value;
            }
        }
 
        private string[] tags;
 
        [Parameter]
        public string[] Tags
        {
            get
            {
                return tags;
            }
            set
            {
                tags = value;
            }
        }
 
        private bool getToken;
 
        [Parameter]
        public SwitchParameter GetToken
        {
            get
            {
                return getToken;
            }
            set
            {
                getToken = value;
            }
        }
 
        #endregion
 
        protected override void BeginProcessing()
        {
            imageList = new List<FileInfo>();
        }
 
        protected override void ProcessRecord()
        {
            if ( images != null )
            {
                foreach ( FileInfo image in images )
                {
                    imageList.Add( (FileInfo)image );
                }
            }
        }
 
        protected override void EndProcessing()
        {
            try
            {
                // obtain an API key and shared secret here:
                // http://www.flickr.com/services/api/misc.api_keys.html
                string flickrApiKey = "<API key>";
                string flickrApiSharedSecret = "shared secret";
                string flickrAuthenticationToken = (string)GetVariableValue( "FlickrToken" );
 
                Flickr flickr = new Flickr( flickrApiKey, flickrApiSharedSecret );
 
                if ( string.IsNullOrEmpty( flickrAuthenticationToken ) )
                {
                    string frob = flickr.AuthGetFrob();
                    string flickrUrl = flickr.AuthCalcUrl( frob, AuthLevel.Write );
 
                    Process browserProcess = Process.Start( "iexplore.exe", flickrUrl );
 
                    browserProcess.WaitForExit();
 
                    Auth authentication = flickr.AuthGetToken( frob );
 
                    flickrAuthenticationToken = authentication.Token;
                }
 
                if ( getToken )
                {
                    WriteObject( flickrAuthenticationToken );
                }
                else
                {
                    flickr.AuthToken = flickrAuthenticationToken;
 
                    string tagString = "";
 
                    if ( tags != null )
                    {
                        tagString = string.Join( ", ", tags );
                    }
 
                    foreach ( FileInfo image in imageList )
                    {
                        if ( ShouldProcess( image.Name ) )
                        {
                            string photoId = flickr.UploadPicture(
                                image.FullName, image.Name, image.Name, tagString );
 
                            WriteObject( flickr.PhotosGetInfo( photoId ) );
                        }
                    }
                }
            }
            finally
            {
                images = null;
            }
        }
    }
}


This Cmdlet obviously requires an account with Flickr, as well as an API Key and Shared Secret that can be obtained here. Using these two strings, a FlickrNet.Flickr object is created, which can then be used to upload photos. An authentication token is also necessary, and Upload-Flickr requires that this token is in a global PowerShell variable called "$FlickrToken". I did this so the token could be stored in that variable in a profile or other dot-sourced script. The Upload-Flickr Cmdlet can also be used to obtain the token, with the GetToken parameter:

PSH$ $FlickrToken = Upload-Flickr -GetToken

When the GetToken parameter is specified, the user is directed to a Flickr website where they must log in. The token is returned after a successful login. No images are processed when this parameter is used. Upload-Flickr takes FileInfo objects as input, as well as an optional array of tags to associate with each image:

PSH$ Get-ChildItem *.png | Upload-Flickr -Tags "vacation", "moon"

For each image that is processed by Upload-Flickr, a FlickrNet.PhotoInfo object is written to the pipeline. The FlickrNet.PhotoInfo object has all kinds of useful information associated with it, including URLs to small, medium, and large versions of the photo.

The next weapon I created for my wife's post-creating arsenal is Post-Blogger:

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.IO;
using System.Net;
using System.Security;
using Google.GData.Client;
 
namespace CustomCmdlets
{
    [Cmdlet( "Post", "Blogger", SupportsShouldProcess = true )]
    public class PostBlogger : PSCmdlet
    {
        private StringBuilder contentStringBuilder;
 
        #region Parameters
 
        private string input;
 
        [Parameter( ValueFromPipeline = true )]
        [AllowNull]
        [AllowEmptyString]
        public string Input
        {
            get
            {
                return input;
            }
            set
            {
                input = value;
            }
        }
 
        private string username;
 
        [Parameter( Mandatory = true )]
        [ValidateNotNullOrEmpty]
        public string Username
        {
            get
            {
                return username;
            }
            set
            {
                username = value;
            }
        }
 
        private string password;
 
        [Parameter( Mandatory = true )]
        [ValidateNotNullOrEmpty]
        public string Password
        {
            get
            {
                return password;
            }
            set
            {
                password = value;
            }
        }
 
        private string blogName;
 
        [Parameter]
        public string BlogName
        {
            get
            {
                return blogName;
            }
            set
            {
                blogName = value;
            }
        }
 
        private string title;
 
        [Parameter( Mandatory = true )]
        [ValidateNotNullOrEmpty]
        public string Title
        {
            get
            {
                return title;
            }
            set
            {
                title = value;
            }
        }
 
        private string content;
 
        [Parameter( Mandatory = true )]
        [ValidateNotNullOrEmpty]
        public string Content
        {
            get
            {
                return content;
            }
            set
            {
                content = value;
            }
        }
 
        private bool draft = false;
 
        [Parameter]
        public SwitchParameter Draft
        {
            get
            {
                return draft;
            }
            set
            {
                draft = value;
            }
        }
 
        #endregion
 
        protected override void BeginProcessing()
        {
            contentStringBuilder = new StringBuilder( content );
 
            if ( contentStringBuilder.Length > 0 )
            {
                contentStringBuilder.Append( Environment.NewLine );
            }
        }
 
        protected override void ProcessRecord()
        {
            if ( !string.IsNullOrEmpty( input ) )
            {
                string line = ( input ).TrimEnd( null );
 
                if ( ShouldProcess( line ) )
                {
                    contentStringBuilder.AppendLine( line );
                }
            }
        }
 
        protected override void EndProcessing()
        {
            try
            {
                Service service = new Service( "blogger", "BloggerCmdlet" );
                NetworkCredential credentials = new NetworkCredential( username, password );
                service.Credentials = new GDataCredentials( username, password );
 
                FeedQuery query = new FeedQuery();
                query.Uri = new Uri( "http://www.blogger.com/feeds/default/blogs" );
 
                AtomFeed bloggerFeed = service.Query( query );
                string feedUri = null;
 
                // if a blog name is provided, find the appropriate feed
                if ( bloggerFeed != null && this.BlogName != null && bloggerFeed.Entries.Count > 0 )
                {
                    for ( int i = 0; feedUri == null && i < bloggerFeed.Entries.Count; i++ )
                    {
                        if ( bloggerFeed.Entries[ i ].Title.Text == this.BlogName )
                        {
                            feedUri = bloggerFeed.Entries[ i ].FeedUri;
                        }
                    }
 
                    if ( feedUri == null )
                    {
                        WriteError( 
                            new ErrorRecord( new Exception( string.Format( "No blog found named \"{0}\".", this.BlogName ) ), 
                            "Post-Blogger", ErrorCategory.InvalidArgument, bloggerFeed ) );
                    }
                }
                else
                {
                    feedUri = bloggerFeed.Entries[ 0 ].FeedUri;
                }
 
                if ( feedUri != null )
                {
                    AtomEntry post = new AtomEntry();
 
                    post.Title = new AtomTextConstruct( AtomTextConstructElementType.Title, title );
 
                    post.Content = new AtomContent();
                    post.Content.Type = "html";
                    post.Content.Content = contentStringBuilder.ToString();
 
                    post.IsDraft = draft;
 
                    AtomEntry createdPost = service.Insert( new Uri( feedUri ), post );
                }
            }
            finally
            {
                contentStringBuilder = null;
            }
        }
    }
}


Post-Blogger is also quite simple. It obviously requires an account with Blogger as well as at least one blog. If you have more than one blog, the BlogName parameter can be used to specify which blog to use; the first blog is used if no name is specified, or if there is only one blog. Nothing too exciting here.

At this point in the project, these Cmdlets worked well, but if I expected my wife to take advantage of them, I needed to make them easy (read: no command line) to use. I put together a PowerShell script that displays a Windows Forms dialog with fields for everything needed to create a Blogger post as well as a path to an image directory. I created a shortcut in the Quick Launch bar that starts the script, and the rest is history. My wife now uses the script and these Cmdlets every Sunday night, and she loves every minute of it. Here is the script:

[System.Reflection.Assembly]::LoadWithPartialName( "System.Windows.Forms" ) | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName( "System.Drawing" ) | Out-Null

[System.Windows.Forms.Application]::EnableVisualStyles()

$username = ""
$password = ""
$title = ""
$content = ""
$draft = $true
$imageDirectory = ""

$postForm = New-Object System.Windows.Forms.Form
$postForm.Text = "New Blogger Post"

$usernameLabel = New-Object System.Windows.Forms.Label
$usernameLabel.Text = "Blogger &Username:"
$usernameLabel.AutoSize = $true
$usernameLabel.Location = New-Object System.Drawing.Point( 4, 8 )
$usernameLabel.Size = New-Object System.Drawing.Size( 97, 13 )
$usernameLabel.TabIndex = 0

$usernameTextBox = New-Object System.Windows.Forms.TextBox
$usernameTextBox.Location = New-Object System.Drawing.Point( 104, 4 )
$usernameTextBox.Size = New-Object System.Drawing.Size( 184, 20 )
$usernameTextBox.TabIndex = 1

$passwordLabel = New-Object System.Windows.Forms.Label
$passwordLabel.Text = "Blogger &Password:"
$passwordLabel.AutoSize = $true
$passwordLabel.Location = New-Object System.Drawing.Point( 4, 32 )
$passwordLabel.Size = New-Object System.Drawing.Size( 95, 13 )
$passwordLabel.TabIndex = 2

$passwordTextBox = New-Object System.Windows.Forms.TextBox
$passwordTextBox.Location = New-Object System.Drawing.Point( 104, 28 )
$passwordTextBox.Size = New-Object System.Drawing.Size( 184, 20 )
$passwordTextBox.UseSystemPasswordChar = $true
$passwordTextBox.TabIndex = 3

$titleLabel = New-Object System.Windows.Forms.Label
$titleLabel.Text = "Post &Title:"
$titleLabel.AutoSize = $true
$titleLabel.Location = New-Object System.Drawing.Point( 4, 56 )
$titleLabel.Size = New-Object System.Drawing.Size( 54, 13 )
$titleLabel.TabIndex = 4

$titleTextBox = New-Object System.Windows.Forms.TextBox
$titleTextBox.Location = New-Object System.Drawing.Point( 104, 52 )
$titleTextBox.Size = New-Object System.Drawing.Size( 184, 20 )
$titleTextBox.TabIndex = 5

$draftCheckBox = New-Object System.Windows.Forms.CheckBox
$draftCheckBox.Text = "&Draft"
$draftCheckBox.Checked = $true
$draftCheckBox.UseVisualStyleBackColor = $true
$draftCheckBox.AutoSize = $true
$draftCheckBox.Location = New-Object System.Drawing.Point( 240, 80 )
$draftCheckBox.Size = New-Object System.Drawing.Size( 49, 15 )
$draftCheckBox.TabIndex = 6

$contentLabel = New-Object System.Windows.Forms.Label
$contentLabel.Text = "Post &Content:"
$contentLabel.AutoSize = $true
$contentLabel.Location = New-Object System.Drawing.Point( 4, 80 )
$contentLabel.Size = New-Object System.Drawing.Size( 71, 13 )
$contentLabel.TabIndex = 7

$contentTextBox = New-Object System.Windows.Forms.TextBox
$contentTextBox.Text = "Pictures..."
$contentTextBox.Multiline = $true
$contentTextBox.Location = New-Object System.Drawing.Point( 4, 96 )
$contentTextBox.Size = New-Object System.Drawing.Size( 284, 76 )
$contentTextBox.TabIndex = 8

$imagesLabel = New-Object System.Windows.Forms.Label
$imagesLabel.Text = "&Image Directory:"
$imagesLabel.AutoSize = $true
$imagesLabel.Location = New-Object System.Drawing.Point( 4, 180 )
$imagesLabel.Size = New-Object System.Drawing.Size( 84, 13 )
$imagesLabel.TabIndex = 9

$imagesTextBox = New-Object System.Windows.Forms.TextBox
$imagesTextBox.Location = New-Object System.Drawing.Point( 104, 176 )
$imagesTextBox.Size = New-Object System.Drawing.Size( 120, 20 )
$imagesTextBox.TabIndex = 10

$browseButton = New-Object System.Windows.Forms.Button
$browseButton.Text = "&Browse..."
$browseButton.UseVisualStyleBackColor = $true
$browseButton.Location = New-Object System.Drawing.Point( 228, 176 )
$browseButton.Size = New-Object System.Drawing.Size( 60, 20 )
$browseButton.TabIndex = 11
$browseButton.add_Click( [System.EventHandler]{
    $shell = New-Object -Com Shell.Application

    $desktop = [Environment]::GetFolderPath( [System.Environment+SpecialFolder]::Desktop )

    $imageFolder = $shell.BrowseForFolder( 0, 
        "Browse to the location of the images you want in this post.", 0, $desktop )

    if ( $imageFolder -ne $null )
    {
        $imagesTextBox.Text = $imageFolder.Self.Path
    }
} )

$postButton = New-Object System.Windows.Forms.Button
$postButton.Text = "P&ost!"
$postButton.UseVisualStyleBackColor = $true
$postButton.Location = New-Object System.Drawing.Point( 164, 204 )
$postButton.Size = New-Object System.Drawing.Size( 60, 20 )
$postButton.TabIndex = 12
$postButton.add_Click( [System.EventHandler]{
    $username = $usernameTextBox.Text
    $password = $passwordTextBox.Text
    $title = $titleTextBox.Text
    $content = $contentTextBox.Text
    $draft = $draftCheckBox.Checked
    $imageDirectory = $imagesTextBox.Text

    $message = ""

    if ( $username -eq "" )
    {
        $message = "You must supply a Blogger username.`n"
    }
    if ( $password -eq "" )
    {
        $message += "You must supply a Blogger password.`n"
    }
    if ( $title -eq "" )
    {
        $message += "You must supply a post title.`n"
    }
    if ( $content -eq "" )
    {
        $message += "You must supply some post content."
    }

    if ( $message -ne "" )
    {
        [System.Windows.Forms.MessageBox]::Show( $message, "Error!" )
    }
    else
    {
        $postForm.DialogResult = [System.Windows.Forms.DialogResult]::OK
        $postForm.Close()
    }
} )

$closeButton = New-Object System.Windows.Forms.Button
$closeButton.Text = "C&lose"
$closeButton.UseVisualStyleBackColor = $true
$closeButton.Location = New-Object System.Drawing.Point( 228, 204 )
$closeButton.Size = New-Object System.Drawing.Size( 60, 20 )
$closeButton.TabIndex = 13
$closeButton.add_Click( [System.EventHandler]{
    $postForm.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
    $postForm.Close()
} )

$postForm.AutoScaleDimensions = New-Object System.Drawing.SizeF( 6, 13 )
$postForm.AutoScaleMode = [System.Windows.Forms.AutoScaleMode]::Font
$postForm.ClientSize = New-Object System.Drawing.Size( 300, 236 )
$postForm.MinimumSize = New-Object System.Drawing.Size( 308, 263 )
$postForm.MaximumSize = New-Object System.Drawing.Size( 308, 263 )
$postForm.SizeGripStyle = [System.Windows.Forms.SizeGripStyle]::Hide
$postForm.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
$postForm.Controls.Add( $closeButton )
$postForm.Controls.Add( $postButton )
$postForm.Controls.Add( $browseButton )
$postForm.Controls.Add( $imagesTextBox )
$postForm.Controls.Add( $imagesLabel )
$postForm.Controls.Add( $contentTextBox )
$postForm.Controls.Add( $contentLabel )
$postForm.Controls.Add( $draftCheckBox )
$postForm.Controls.Add( $titleTextBox )
$postForm.Controls.Add( $titleLabel )
$postForm.Controls.Add( $passwordTextBox )
$postForm.Controls.Add( $passwordLabel )
$postForm.Controls.Add( $usernameTextBox )
$postForm.Controls.Add( $usernameLabel )
$postForm.MaximizeBox = $false
$postForm.MinimizeBox = $false
$postForm.ResumeLayout( $false )
$postForm.PerformLayout()

$imageHtml = ""
$blogName = "<blog name>"

if ( $postForm.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK )
{
    if ( $imageDirectory -ne "" )
    {
        $imageHTML = Get-ChildItem $imageDirectory | 
            Where-Object { !$_.PSIsContainer } |
            Upload-Flickr -Tags ( [datetime]::Now.ToString( "dd-MM-yyyy" ) ) -Verbose | 
            Foreach-Object {
                "<div style=`"text-align: center;`"><a href=`"{0}`"><img src=`"{1}`" /></a></div><br><br>" `
                    -f $_.LargeUrl, $_.MediumUrl
            }

        if ( $draft )
        {
            $imageHTML | Post-Blogger -Username $username -Password $password `
                                      -BlogName $blogName -Title $title -Content $content -Draft
        }
        else
        {
            $imageHTML | Post-Blogger -Username $username -Password $password `
                                      -BlogName $blogName -Title $title -Content $content
        }
    }
    else
    {
        if ( $draft )
        {
            Post-Blogger -Username $username -Password $password `
                         -BlogName $blogName -Title $title -Content $content -Draft
        }
        else
        {
            Post-Blogger -Username $username -Password $password `
                         -BlogName $blogName -Title $title -Content $content
        }
    }
}


Now, this script and these Cmdlets are probably the most likely to not be used by anyone else, but I think I am the most pleased with them, since they made my wife's life so much easier. I benefit from PowerShell every day, so it was fun to see my non-technical wife gain a little respect for the lowly command line. I'll finish this post as I did the last one: with the XML help for these two Cmdlets:

<?xml version="1.0" encoding="utf-8" ?>
<helpItems xmlns="http://msh" schema="maml">
    <command:command xmlns:maml="http://schemas.microsoft.com/maml/2004/10" xmlns:command="http://schemas.microsoft.com/maml/dev/command/2004/10" xmlns:dev="http://schemas.microsoft.com/maml/dev/2004/10">
        <command:details>
            <command:name>Upload-Flickr</command:name>
            <maml:description>
                <maml:para>The Upload-Flickr Cmdlet uses the FlickrNet library to upload images to a Flickr account.</maml:para>
            </maml:description>
            <command:verb>Upload</command:verb>
            <command:noun>Flickr</command:noun>
        </command:details>
        <maml:description>
            <maml:para>
            The Upload-Flickr Cmdlet uses the FlickrNet library to upload images to a Flickr account.  Flickr authentication will be performed each time the Cmdlet is run.  To avoid manual authentication, the Flickr token can be specified in your PowerShell profile by creating the following variable in the global scope:
 
            $FlickrToken = "Flickr token"
            </maml:para>
        </maml:description>
        <command:syntax>
            <command:syntaxItem>
                <maml:name>Upload-Flickr</maml:name>
                <command:parameter required="true">
                    <maml:name>images</maml:name>
                    <command:parameterValue required="true">FileInfo []</command:parameterValue>
                </command:parameter>
                <command:parameter required="false">
                    <maml:name>tags</maml:name>
                    <command:parameterValue required="false">string []</command:parameterValue>
                </command:parameter>
                <command:parameter required="false">
                    <maml:name>getToken</maml:name>
                </command:parameter>
            </command:syntaxItem>
        </command:syntax>
        <command:parameters>
            <command:parameter required="false" position="named" globbing="false" pipelineInput="true">
                <maml:name>Images</maml:name>
                <maml:description>
                    <maml:para>The image(s) to upload to Flickr.</maml:para>
                </maml:description>
                <command:parameterValue required="true">FileInfo []</command:parameterValue>
            </command:parameter>
            <command:parameter required="false" position="named" globbing="false" pipelineInput="true">
                <maml:name>Tags</maml:name>
                <maml:description>
                    <maml:para>The tag(s) to associate with the images.</maml:para>
                </maml:description>
                <command:parameterValue required="false">string []</command:parameterValue>
            </command:parameter>
            <command:parameter required="false" position="named" globbing="false" pipelineInput="false">
                <maml:name>GetToken</maml:name>
                <maml:description>
                    <maml:para>Retrieves an authentication token.  No images will be processed if this option is specified.</maml:para>
                </maml:description>
                <command:parameterValue required="true">SwitchParameter</command:parameterValue>
            </command:parameter>
        </command:parameters>
        <command:inputTypes>
            <command:inputType>
                <dev:type>
                    <maml:name>FileInfo []</maml:name>
                    <maml:uri/>
                    <maml:description>
                        <maml:para>
                            The image(s) to upload to Flickr.
                        </maml:para>
                    </maml:description>
                </dev:type>
                <maml:description></maml:description>
            </command:inputType>
        </command:inputTypes>
        <command:returnValues>
            <command:returnValue>
                <dev:type>
                    <maml:name>FlickrNet.PhotoInfo []</maml:name>
                    <maml:uri />
                    <maml:description>
                        <maml:para>
                            Each PhotoInfo object contains information about an uploaded image.
                        </maml:para>
                    </maml:description>
                </dev:type>   
                <maml:description></maml:description> 
            </command:returnValue>
        </command:returnValues>
    </command:command>
    <command:command xmlns:maml="http://schemas.microsoft.com/maml/2004/10" xmlns:command="http://schemas.microsoft.com/maml/dev/command/2004/10" xmlns:dev="http://schemas.microsoft.com/maml/dev/2004/10">
        <command:details>
            <command:name>Post-Blogger</command:name>
            <maml:description>
                <maml:para>The Post-Bloger Cmdlet uses the Google.GData.Client library to post to Blogger.</maml:para>
            </maml:description>
            <command:verb>Post</command:verb>
            <command:noun>Blogger</command:noun>
        </command:details>
        <maml:description>
            <maml:para>The Post-Blogger Cmdlet uses the Google.GData.Client library to post to Blogger.</maml:para>
        </maml:description>
        <command:syntax>
            <command:syntaxItem>
                <maml:name>Post-Blogger</maml:name>
                <command:parameter required="true">
                    <maml:name>username</maml:name>
                    <command:parameterValue required="true">string</command:parameterValue>
                </command:parameter>
                <command:parameter required="true">
                    <maml:name>password</maml:name>
                    <command:parameterValue required="true">string</command:parameterValue>
                </command:parameter>
                <command:parameter required="true">
                    <maml:name>blogName</maml:name>
                    <command:parameterValue required="true">string</command:parameterValue>
                </command:parameter>
                <command:parameter required="true">
                    <maml:name>title</maml:name>
                    <command:parameterValue required="true">string</command:parameterValue>
                </command:parameter>
                <command:parameter required="true">
                    <maml:name>content</maml:name>
                    <command:parameterValue required="true">string</command:parameterValue>
                </command:parameter>
                <command:parameter required="false">
                    <maml:name>input</maml:name>
                    <command:parameterValue required="true">string</command:parameterValue>
                </command:parameter>
                <command:parameter required="false">
                    <maml:name>draft</maml:name>
                </command:parameter>
            </command:syntaxItem>
        </command:syntax>
        <command:parameters>
            <command:parameter required="true" position="named" globbing="false" pipelineInput="false">
                <maml:name>Username</maml:name>
                <maml:description>
                    <maml:para>Blogger account username.</maml:para>
                </maml:description>
                <command:parameterValue required="true">string</command:parameterValue>
            </command:parameter>
            <command:parameter required="true" position="named" globbing="false" pipelineInput="false">
                <maml:name>Password</maml:name>
                <maml:description>
                    <maml:para>Blogger account password.</maml:para>
                </maml:description>
                <command:parameterValue required="true">string</command:parameterValue>
            </command:parameter>
            <command:parameter required="true" position="named" globbing="false" pipelineInput="false">
                <maml:name>BlogName</maml:name>
                <maml:description>
                    <maml:para>The name of the blog.  This parameter is only necessary if the Blogger account being used has more than one blog.</maml:para>
                </maml:description>
                <command:parameterValue required="true">string</command:parameterValue>
            </command:parameter>
            <command:parameter required="true" position="named" globbing="false" pipelineInput="false">
                <maml:name>Title</maml:name>
                <maml:description>
                    <maml:para>The title of the post.</maml:para>
                </maml:description>
                <command:parameterValue required="true">string</command:parameterValue>
            </command:parameter>
            <command:parameter required="true" position="named" globbing="false" pipelineInput="false">
                <maml:name>Content</maml:name>
                <maml:description>
                    <maml:para>The content of the post.</maml:para>
                </maml:description>
                <command:parameterValue required="true">string</command:parameterValue>
            </command:parameter>
            <command:parameter required="false" position="named" globbing="false" pipelineInput="true">
                <maml:name>Input</maml:name>
                <maml:description>
                    <maml:para>Pipeline input that will be added to the content of the post.</maml:para>
                </maml:description>
                <command:parameterValue required="true">string</command:parameterValue>
            </command:parameter>
            <command:parameter required="false" position="named" globbing="false" pipelineInput="false">
                <maml:name>Draft</maml:name>
                <maml:description>
                    <maml:para>If specified, the post will be marked as a draft.</maml:para>
                </maml:description>
                <command:parameterValue required="true">SwitchParameter</command:parameterValue>
            </command:parameter>
        </command:parameters>
        <command:inputTypes>
            <command:inputType>
                <dev:type>
                    <maml:name>String []</maml:name>
                    <maml:uri/>
                    <maml:description>
                        <maml:para>
                            Text to be added to the content of the post.
                        </maml:para>
                    </maml:description>
                </dev:type>
                <maml:description></maml:description>
            </command:inputType>
        </command:inputTypes>
    </command:command>
</helpItems>


Enjoy.

No comments: