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*:
When defining the provider class, a Windows PowerShell content provider might declare provider capabilities of ExpandWildcards, Filter, Include, or Exclude, from the System.Management.Automation.Provider.Providercapabilities enumeration. In these cases, the implementation of the System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentreader* method must ensure that the path passed to the method meets the requirements of the specified capabilities. To do this, the method should access the appropriate property, for example, the System.Management.Automation.Provider.Cmdletprovider.Exclude* and System.Management.Automation.Provider.Cmdletprovider.Include* properties.
By default, overrides of this method should not retrieve a reader for objects that are hidden from the user unless the System.Management.Automation.Provider.Cmdletprovider.Force* property is set to
true
. An error should be written if the path represents an item that is hidden from the user and System.Management.Automation.Provider.Cmdletprovider.Force* is set tofalse
.
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*:
When defining the provider class, a Windows PowerShell content provider might declare provider capabilities of ExpandWildcards, Filter, Include, or Exclude, from the System.Management.Automation.Provider.Providercapabilities enumeration. In these cases, the implementation of the System.Management.Automation.Provider.Icontentcmdletprovider.Getcontentwriter* method must ensure that the path passed to the method meets the requirements of the specified capabilities. To do this, the method should access the appropriate property, for example, the System.Management.Automation.Provider.Cmdletprovider.Exclude* and System.Management.Automation.Provider.Cmdletprovider.Include* properties.
By default, overrides of this method should not retrieve a writer for objects that are hidden from the user unless the System.Management.Automation.Provider.Cmdletprovider.Force* property is set to
true
. An error should be written if the path represents an item that is hidden from the user and System.Management.Automation.Provider.Cmdletprovider.Force* is set tofalse
.
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*:
When defining the provider class, a Windows PowerShell content provider might declare provider capabilities of ExpandWildcards, Filter, Include, or Exclude, from the System.Management.Automation.Provider.Providercapabilities enumeration. In these cases, the implementation of the System.Management.Automation.Provider.Icontentcmdletprovider.Clearcontent* method must ensure that the path passed to the method meets the requirements of the specified capabilities. To do this, the method should access the appropriate property, for example, the System.Management.Automation.Provider.Cmdletprovider.Exclude* and System.Management.Automation.Provider.Cmdletprovider.Include* properties.
By default, overrides of this method should not clear the contents of objects that are hidden from the user unless the System.Management.Automation.Provider.Cmdletprovider.Force* property is set to
true
. An error should be written if the path represents an item that is hidden from the user and System.Management.Automation.Provider.Cmdletprovider.Force* is set tofalse
.Your implementation of the System.Management.Automation.Provider.Icontentcmdletprovider.Clearcontent* method should call System.Management.Automation.Provider.Cmdletprovider.ShouldProcess and verify its return value before making any changes to the data store. This method is used to confirm execution of an operation when a change is made to the data store, such as clearing content. The System.Management.Automation.Provider.Cmdletprovider.ShouldProcess method sends the name of the resource to be changed to the user, with the Windows PowerShell runtime handling any command-line settings or preference variables in determining what should be displayed.
After the call to System.Management.Automation.Provider.Cmdletprovider.ShouldProcess returns
true
, the System.Management.Automation.Provider.Icontentcmdletprovider.Clearcontent* method should call the System.Management.Automation.Provider.Cmdletprovider.ShouldContinue method. This method sends a message to the user to allow feedback to verify if the operation should be continued. The call to System.Management.Automation.Provider.Cmdletprovider.ShouldContinue allows an additional check for potentially dangerous system modifications.
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