Creating a Windows PowerShell Content Provider

This topic describes how to create a Windows PowerShell provider that enables the user to manipulate the contents of the items in a data store. As a consequence, a provider that can manipulate the contents of items is referred to as a Windows PowerShell content provider.

Note

You can download the C# source file (AccessDBSampleProvider06.cs) for this provider using the Microsoft Windows Software Development Kit for Windows Vista and .NET Framework 3.0 Runtime Components. For download instructions, see How to Install Windows PowerShell and Download the Windows PowerShell SDK. The downloaded source files are available in the <PowerShell Samples> directory. For more information about other Windows PowerShell provider implementations, see Designing Your Windows PowerShell Provider.

Define the Windows PowerShell Content Provider Class

A Windows PowerShell content provider must create a .NET class that supports the System.Management.Automation.Provider.Icontentcmdletprovider interface. Here is the class definition for the item provider described in this section.

[CmdletProvider("AccessDB", ProviderCapabilities.None)]
public class AccessDBProvider : NavigationCmdletProvider, IContentCmdletProvider

Note that in this class definition, the System.Management.Automation.Provider.Cmdletproviderattribute attribute includes two parameters. The first parameter specifies a user-friendly name for the provider that is used by Windows PowerShell. The second parameter specifies the Windows PowerShell specific capabilities that the provider exposes to the Windows PowerShell runtime during command processing. For this provider, there are no added Windows PowerShell specific capabilities.

Define Functionality of Base Class

As described in Design Your Windows PowerShell Provider, the System.Management.Automation.Provider.Navigationcmdletprovider class derives from several other classes that provided different provider functionality. A Windows PowerShell content provider, therefore, typically defines all of the functionality provided by those classes.

For more information about how to implement functionality for adding session-specific initialization information and for releasing resources that are used by the provider, see Creating a Basic Windows PowerShell Provider. However, most providers, including the provider described here, can use the default implementation of this functionality that is provided by Windows PowerShell.

To access the data store, the provider must implement the methods of the System.Management.Automation.Provider.Drivecmdletprovider base class. For more information about implementing these methods, see Creating a Windows PowerShell Drive Provider.

To manipulate the items of a data store, such as getting, setting, and clearing items, the provider must implement the methods provided by the System.Management.Automation.Provider.Itemcmdletprovider base class. For more information about implementing these methods, see Creating a Windows PowerShell Item Provider.

To work on multi-layer data stores, the provider must implement the methods provided by the System.Management.Automation.Provider.Containercmdletprovider base class. For more information about implementing these methods, see Creating a Windows PowerShell Container Provider.

To support recursive commands, nested containers, and relative paths, the provider must implement the System.Management.Automation.Provider.Navigationcmdletprovider base class. In addition, this Windows PowerShell content provider can attaches System.Management.Automation.Provider.Icontentcmdletprovider interface to the System.Management.Automation.Provider.Navigationcmdletprovider base class, and must therefore implement the methods provided by that class. For more information, see implementing those methods, see Implement a Navigation Windows PowerShell Provider.

Implementing a Content Reader

To read content from an item, a provider must implements a content reader class that derives from System.Management.Automation.Provider.Icontentreader. The content reader for this provider allows access to the contents of a row in a data table. The content reader class defines a Read method that retrieves the data from the indicated row and returns a list representing that data, a Seek method that moves the content reader, a Close method that closes the content reader, and a Dispose method.

public class AccessDBContentReader : IContentReader
{
    // A provider instance is required so as to get "content"
    private AccessDBProvider provider;
    private string path;
    private long currentOffset;

    internal AccessDBContentReader(string path, AccessDBProvider provider)
    {
        this.path = path;
        this.provider = provider;
    }

    /// <summary>
    /// Read the specified number of rows from the source.
    /// </summary>
    /// <param name="readCount">The number of items to 
    /// return.</param>
    /// <returns>An array of elements read.</returns>
    public IList Read(long readCount)
    {
        // Read the number of rows specified by readCount and increment
        // offset
        string tableName;
        int rowNumber;
        PathType type = provider.GetNamesFromPath(path, out tableName, out rowNumber);

        Collection<DatabaseRowInfo> rows =
            provider.GetRows(tableName);
        Collection<DataRow> results = new Collection<DataRow>();

        if (currentOffset < 0 || currentOffset >= rows.Count)
        {
            return null;
        }

        int rowsRead = 0;

        while (rowsRead < readCount && currentOffset < rows.Count)
        {
            results.Add(rows[(int)currentOffset].Data);
            rowsRead++;
            currentOffset++;
        }

        return results;
    } // Read

    /// <summary>
    /// Moves the content reader specified number of rows from the 
    /// origin
    /// </summary>
    /// <param name="offset">Number of rows to offset</param>
    /// <param name="origin">Starting row from which to offset</param>
    public void Seek(long offset, System.IO.SeekOrigin origin)
    {
        // get the number of rows in the table which will help in
        // calculating current position
        string tableName;
        int rowNumber;

        PathType type = provider.GetNamesFromPath(path, out tableName, out rowNumber);

        if (type == PathType.Invalid)
        {
            throw new ArgumentException("Path specified must represent a table or a row :" + path);
        }

        if (type == PathType.Table)
        {
            Collection<DatabaseRowInfo> rows = provider.GetRows(tableName);

            int numRows = rows.Count;

            if (offset > rows.Count)
            {
                throw new
                       ArgumentException(
                           "Offset cannot be greater than the number of rows available"
                                        );
            }

            if (origin == System.IO.SeekOrigin.Begin)
            {
                // starting from Beginning with an index 0, the current offset
                // has to be advanced to offset - 1
                currentOffset = offset - 1;
            }
            else if (origin == System.IO.SeekOrigin.End)
            {
                // starting from the end which is numRows - 1, the current
                // offset is so much less than numRows - 1
                currentOffset = numRows - 1 - offset;
            }
            else
            {
                // calculate from the previous value of current offset
                // advancing forward always
                currentOffset += offset;
            }
        } // if (type...
        else
        {
            // for row, the offset will always be set to 0
            currentOffset = 0;
        }

    } // Seek

    /// <summary>
    /// Closes the content reader, so all members are reset
    /// </summary>
    public void Close()
    {
        Dispose();
    } // Close

    /// <summary>
    /// Dispose any resources being used
    /// </summary>
    public void Dispose()
    {
        Seek(0, System.IO.SeekOrigin.Begin);
        
        GC.SuppressFinalize(this);
    } // Dispose
} // AccessDBContentReader

Implementing a Content Writer

To write content to an item, a provider must implement a content writer class derives from System.Management.Automation.Provider.Icontentwriter. The content writer class defines a Write method that writes the specified row content, a Seek method that moves the content writer, a Close method that closes the content writer, and a Dispose method.

public class AccessDBContentWriter : IContentWriter
{
    // A provider instance is required so as to get "content"
    private AccessDBProvider provider;
    private string path;
    private long currentOffset;

    internal AccessDBContentWriter(string path, AccessDBProvider provider)
    {
        this.path = path;
        this.provider = provider;
    }

    /// <summary>
    /// Write the specified row contents in the source
    /// </summary>
    /// <param name="content"> The contents to be written to the source.
    /// </param>
    /// <returns>An array of elements which were successfully written to 
    /// the source</returns>
    /// 
    public IList Write(IList content)
    {
        if (content == null)
        {
            return null;
        }

        // Get the total number of rows currently available it will 
        // determine how much to overwrite and how much to append at
        // the end
        string tableName;
        int rowNumber;
        PathType type = provider.GetNamesFromPath(path, out tableName, out rowNumber);

        if (type == PathType.Table)
        {
            OdbcDataAdapter da = provider.GetAdapterForTable(tableName);
            if (da == null)
            {
                return null;
            }

            DataSet ds = provider.GetDataSetForTable(da, tableName);
            DataTable table = provider.GetDataTable(ds, tableName);

            string[] colValues = (content[0] as string).Split(',');

            // set the specified row
            DataRow row = table.NewRow();

            for (int i = 0; i < colValues.Length; i++)
            {
                if (!String.IsNullOrEmpty(colValues[i]))
                {
                    row[i] = colValues[i];
                }
            }

            //table.Rows.InsertAt(row, rowNumber);
            // Update the table
            table.Rows.Add(row);
            da.Update(ds, tableName);
            
        }
        else 
        {
            throw new InvalidOperationException("Operation not supported. Content can be added only for tables");
        }

        return null;
    } // Write

    /// <summary>
    /// Moves the content reader specified number of rows from the 
    /// origin
    /// </summary>
    /// <param name="offset">Number of rows to offset</param>
    /// <param name="origin">Starting row from which to offset</param>
    public void Seek(long offset, System.IO.SeekOrigin origin)
    {
        // get the number of rows in the table which will help in
        // calculating current position
        string tableName;
        int rowNumber;

        PathType type = provider.GetNamesFromPath(path, out tableName, out rowNumber);

        if (type == PathType.Invalid)
        {
            throw new ArgumentException("Path specified should represent either a table or a row : " + path);
        }

        Collection<DatabaseRowInfo> rows =
               provider.GetRows(tableName);

        int numRows = rows.Count;

        if (offset > rows.Count)
        {
            throw new
                   ArgumentException(
                       "Offset cannot be greater than the number of rows available"
                                           );
        }

        if (origin == System.IO.SeekOrigin.Begin)
        {
            // starting from Beginning with an index 0, the current offset
            // has to be advanced to offset - 1
            currentOffset = offset - 1;
        }
        else if (origin == System.IO.SeekOrigin.End)
        {
            // starting from the end which is numRows - 1, the current
            // offset is so much less than numRows - 1
            currentOffset = numRows - 1 - offset;
        }
        else
        {
            // calculate from the previous value of current offset
            // advancing forward always
            currentOffset += offset;
        }

    } // Seek

    /// <summary>
    /// Closes the content reader, so all members are reset
    /// </summary>
    public void Close()
    {
        Dispose();
    } // Close

    /// <summary>
    /// Dispose any resources being used
    /// </summary>
    public void Dispose()
    {
        Seek(0, System.IO.SeekOrigin.Begin);

        GC.SuppressFinalize(this);
    } // Dispose
} // AccessDBContentWriter

Retrieving the Content Reader

To get content from an item, the provider must implement the System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentreader* to support the Get-Content cmdlet. This method returns the content reader for the item located at the specified path. The reader object can then be opened to read the content.

Here is the implementation of System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentreader* for this method for this provider.

public IContentReader GetContentReader(string path)
{
    string tableName;
    int rowNumber;

    PathType type = GetNamesFromPath(path, out tableName, out rowNumber);

    if (type == PathType.Invalid)
    {
        ThrowTerminatingInvalidPathException(path);
    }
    else if (type == PathType.Row)
    {
        throw new InvalidOperationException("contents can be obtained only for tables");
    }

    return new AccessDBContentReader(path, this);
} // GetContentReader
public IContentReader GetContentReader(string path)
{
    string tableName;
    int rowNumber;

    PathType type = GetNamesFromPath(path, out tableName, out rowNumber);

    if (type == PathType.Invalid)
    {
        ThrowTerminatingInvalidPathException(path);
    }
    else if (type == PathType.Row)
    {
        throw new InvalidOperationException("contents can be obtained only for tables");
    }

    return new AccessDBContentReader(path, this);
} // GetContentReader

Things to Remember About Implementing GetContentReader

The following conditions may apply to an implementation of System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentreader*:

Attaching Dynamic Parameters to the Get-Content Cmdlet

The Get-Content cmdlet might require additional parameters that are specified dynamically at runtime. To provide these dynamic parameters, the Windows PowerShell content provider must implement the System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentreaderdynamicparameters* method. This method retrieves dynamic parameters for the item at the indicated path and returns an object that has properties and fields with parsing attributes similar to a cmdlet class or a System.Management.Automation.Runtimedefinedparameterdictionary object. The Windows PowerShell runtime uses the returned object to add the parameters to the cmdlet.

This Windows PowerShell container provider does not implement this method. However, the following code is the default implementation of this method.

public object GetContentReaderDynamicParameters(string path)
{
    return null;
}
public object GetContentReaderDynamicParameters(string path)
{
    return null;
}

Retrieving the Content Writer

To write content to an item, the provider must implement the System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentwriter* to support the Set-Content and Add-Content cmdlets. This method returns the content writer for the item located at the specified path.

Here is the implementation of System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentwriter* for this method.

public IContentWriter GetContentWriter(string path)
{
    string tableName;
    int rowNumber;

    PathType type = GetNamesFromPath(path, out tableName, out rowNumber);

    if (type == PathType.Invalid)
    {
        ThrowTerminatingInvalidPathException(path);
    }
    else if (type == PathType.Row)
    {
        throw new InvalidOperationException("contents can be added only to tables");
    }

    return new AccessDBContentWriter(path, this);
}
public IContentWriter GetContentWriter(string path)
{
    string tableName;
    int rowNumber;

    PathType type = GetNamesFromPath(path, out tableName, out rowNumber);

    if (type == PathType.Invalid)
    {
        ThrowTerminatingInvalidPathException(path);
    }
    else if (type == PathType.Row)
    {
        throw new InvalidOperationException("contents can be added only to tables");
    }

    return new AccessDBContentWriter(path, this);
}

Things to Remember About Implementing GetContentWriter

The following conditions may apply to your implementation of System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentwriter*:

Attaching Dynamic Parameters to the Add-Content and Set-Content Cmdlets

The Add-Content and Set-Content cmdlets might require additional dynamic parameters that are added a runtime. To provide these dynamic parameters, the Windows PowerShell content provider must implement the System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentwriterdynamicparameters* method to handle these parameters. This method retrieves dynamic parameters for the item at the indicated path and returns an object that has properties and fields with parsing attributes similar to a cmdlet class or a System.Management.Automation.Runtimedefinedparameterdictionary object. The Windows PowerShell runtime uses the returned object to add the parameters to the cmdlets.

This Windows PowerShell container provider does not implement this method. However, the following code is the default implementation of this method.

public object GetContentWriterDynamicParameters(string path)
{
    return null;
}

Clearing Content

Your content provider implements the System.Management.Automation.Provider.Icontentcmdletprovider.Clearcontent* method in support of the Clear-Content cmdlet. This method removes the contents of the item at the specified path, but leaves the item intact.

Here is the implementation of the System.Management.Automation.Provider.Icontentcmdletprovider.Clearcontent* method for this provider.

public void ClearContent(string path)
{
    string tableName;
    int rowNumber;

    PathType type = GetNamesFromPath(path, out tableName, out rowNumber);

    if (type != PathType.Table)
    {
        WriteError(new ErrorRecord(
            new InvalidOperationException("Operation not supported. Content can be cleared only for table"),
                "NotValidRow", ErrorCategory.InvalidArgument,
                    path));
        return;
    }

    OdbcDataAdapter da = GetAdapterForTable(tableName);

    if (da == null)
    {
        return;
    }

    DataSet ds = GetDataSetForTable(da, tableName);
    DataTable table = GetDataTable(ds, tableName);

    // Clear contents at the specified location
    for (int i = 0; i < table.Rows.Count; i++)
    {
        table.Rows[i].Delete();
    }

    if (ShouldProcess(path, "ClearContent"))
    {
        da.Update(ds, tableName);
    }

} // ClearContent

Things to Remember About Implementing ClearContent

The following conditions may apply to an implementation of System.Management.Automation.Provider.Icontentcmdletprovider.Clearcontent*:

Attaching Dynamic Parameters to the Clear-Content Cmdlet

The Clear-Content cmdlet might require additional dynamic parameters that are added at runtime. To provide these dynamic parameters, the Windows PowerShell content provider must implement the System.Management.Automation.Provider.Icontentcmdletprovider.Clearcontentdynamicparameters* method to handle these parameters. This method retrieves the parameters for the item at the indicated path. This method retrieves dynamic parameters for the item at the indicated path and returns an object that has properties and fields with parsing attributes similar to a cmdlet class or a System.Management.Automation.Runtimedefinedparameterdictionary object. The Windows PowerShell runtime uses the returned object to add the parameters to the cmdlet.

This Windows PowerShell container provider does not implement this method. However, the following code is the default implementation of this method.

public object ClearContentDynamicParameters(string path)
{
    return null;
}
public object ClearContentDynamicParameters(string path)
{
    return null;
}

Code Sample

For complete sample code, see AccessDbProviderSample06 Code Sample.

Defining Object Types and Formatting

When writing a provider, it may be necessary to add members to existing objects or define new objects. When this is done, you must create a Types file that Windows PowerShell can use to identify the members of the object and a Format file that defines how the object is displayed. For more information, see Extending Object Types and Formatting.

Building the Windows PowerShell Provider

See How to Register Cmdlets, Providers, and Host Applications.

Testing the Windows PowerShell Provider

When your Windows PowerShell provider has been registered with Windows PowerShell, you can test it by running the supported cmdlets on the command line. For example, test the sample content provider.

Use the Get-Content cmdlet to retrieve the contents of specified item in the database table at the path specified by the Path parameter. The ReadCount parameter specifies the number of items for the defined content reader to read (default 1). With the following command entry, the cmdlet retrieves two rows (items) from the table and displays their contents. Note that the following example output uses a fictitious Access database.

Get-Content -Path mydb:\Customers -ReadCount 2
ID        : 1
FirstName : Eric
LastName  : Gruber
Email     : ericgruber@fabrikam.com
Title     : President
Company   : Fabrikam
WorkPhone : (425) 555-0100
Address   : 4567 Main Street
City      : Buffalo
State     : NY
Zip       : 98052
Country   : USA
ID        : 2
FirstName : Eva
LastName  : Corets
Email     : evacorets@cohowinery.com
Title     : Sales Representative
Company   : Coho Winery
WorkPhone : (360) 555-0100
Address   : 8910 Main Street
City      : Cabmerlot
State     : WA
Zip       : 98089
Country   : USA

See Also

Creating Windows PowerShell providers

Design Your Windows PowerShell provider

Extending Object Types and Formatting

Implement a Navigation Windows PowerShell provider

How to Register Cmdlets, Providers, and Host Applications

Windows PowerShell SDK

Windows PowerShell Programmer's Guide