Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This topic describes how to implement the methods of a Windows PowerShell provider that support items that contain other items, such as folders in the FileSystem provider. To be able to support containers, a provider must derive from the System.Management.Automation.Provider.ContainerCmdletProvider class.
The provider in the examples in this topic uses an Access database as its data store. There are several helper methods and classes that are used to interact with the database. For the complete sample that includes the helper methods, see AccessDBProviderSample04.
For more information about Windows PowerShell providers, see Windows PowerShell Provider Overview.
Implementing container methods
The System.Management.Automation.Provider.ContainerCmdletProvider class implements methods that support containers, and create, copy, and remove items. For a complete list of these methods, see System.Management.Automation.Provider.ContainerCmdletProvider.
Note
This topic builds on the information in Windows PowerShell Provider QuickStart. This topic does not cover the basics of how to set up a provider project, or how to implement the methods inherited from the System.Management.Automation.Provider.DriveCmdletProvider class that create and remove drives. This topic also does not cover how to implement methods exposed by the System.Management.Automation.Provider.ItemCmdletProvider class. For an example that shows how to implement item cmdlets, see Writing an item provider.
Declaring the provider class
Declare the provider to derive from the System.Management.Automation.Provider.ContainerCmdletProvider class, and decorate it with the System.Management.Automation.Provider.CmdletProviderAttribute.
[CmdletProvider("AccessDB", ProviderCapabilities.None)]
public class AccessDBProvider : ContainerCmdletProvider
{
}
Implementing GetChildItems
The PowerShell engine calls the System.Management.Automation.Provider.ContainerCmdletProvider.GetChildItems* method when a user calls the Microsoft.PowerShell.Commands.GetChildItemCommand cmdlet. This method gets the items that are the children of the item at the specified path.
In the Access database example, the behavior of the System.Management.Automation.Provider.ContainerCmdletProvider.GetChildItems* method depends on the type of the specified item. If the item is the drive, then the children are tables, and the method returns the set of tables from the database. If the specified item is a table, then the children are the rows of that table. If the item is a row, then it has no children, and the method returns that row only. All child items are sent back to the PowerShell engine by the System.Management.Automation.Provider.CmdletProvider.WriteItemObject* method.
protected override void GetChildItems(string path, bool recurse)
{
// If path represented is a drive then the children in the path are
// tables. Hence all tables in the drive represented will have to be
// returned
if (PathIsDrive(path))
{
foreach (DatabaseTableInfo table in GetTables())
{
WriteItemObject(table, path, true);
// if the specified item exists and recurse has been set then
// all child items within it have to be obtained as well
if (ItemExists(path) && recurse)
{
GetChildItems(path + pathSeparator + table.Name, recurse);
}
} // foreach (DatabaseTableInfo...
} // if (PathIsDrive...
else
{
// Get the table name, row number and type of path from the
// path specified
string tableName;
int rowNumber;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
if (type == PathType.Table)
{
// Obtain all the rows within the table
foreach (DatabaseRowInfo row in GetRows(tableName))
{
WriteItemObject(row, path + pathSeparator + row.RowNumber,
false);
} // foreach (DatabaseRowInfo...
}
else if (type == PathType.Row)
{
// In this case the user has directly specified a row, hence
// just give that particular row
DatabaseRowInfo row = GetRow(tableName, rowNumber);
WriteItemObject(row, path + pathSeparator + row.RowNumber,
false);
}
else
{
// In this case, the path specified is not valid
ThrowTerminatingInvalidPathException(path);
}
} // else
}
Implementing GetChildNames
The System.Management.Automation.Provider.ContainerCmdletProvider.GetChildNames* method is similar to the System.Management.Automation.Provider.ContainerCmdletProvider.GetChildItems* method, except that it returns only the name property of the items, and not the items themselves.
protected override void GetChildNames(string path,
ReturnContainers returnContainers)
{
// If the path represented is a drive, then the child items are
// tables. get the names of all the tables in the drive.
if (PathIsDrive(path))
{
foreach (DatabaseTableInfo table in GetTables())
{
WriteItemObject(table.Name, path, true);
} // foreach (DatabaseTableInfo...
} // if (PathIsDrive...
else
{
// Get type, table name and row number from path specified
string tableName;
int rowNumber;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
if (type == PathType.Table)
{
// Get all the rows in the table and then write out the
// row numbers.
foreach (DatabaseRowInfo row in GetRows(tableName))
{
WriteItemObject(row.RowNumber, path, false);
} // foreach (DatabaseRowInfo...
}
else if (type == PathType.Row)
{
// In this case the user has directly specified a row, hence
// just give that particular row
DatabaseRowInfo row = GetRow(tableName, rowNumber);
WriteItemObject(row.RowNumber, path, false);
}
else
{
ThrowTerminatingInvalidPathException(path);
}
} // else
}
Implementing NewItem
The System.Management.Automation.Provider.ContainerCmdletProvider.NewItem* method creates a new item of the specified type at the specified path. The PowerShell engine calls this method when a user calls the Microsoft.PowerShell.Commands.NewItemCommand cmdlet.
In this example, the method implements logic to determine that the path and type match. That is, only a table can be created directly under the drive (the database), and only a row can be created under a table. If the specified path and item type don't match in this way, the method throws an exception.
protected override void NewItem(string path, string type,
object newItemValue)
{
string tableName;
int rowNumber;
PathType pt = GetNamesFromPath(path, out tableName, out rowNumber);
if (pt == PathType.Invalid)
{
ThrowTerminatingInvalidPathException(path);
}
// Check if type is either "table" or "row", if not throw an
// exception
if (!String.Equals(type, "table", StringComparison.OrdinalIgnoreCase)
&& !String.Equals(type, "row", StringComparison.OrdinalIgnoreCase))
{
WriteError(new ErrorRecord
(new ArgumentException("Type must be either a table or row"),
"CannotCreateSpecifiedObject",
ErrorCategory.InvalidArgument,
path
)
);
throw new ArgumentException("This provider can only create items of type \"table\" or \"row\"");
}
// Path type is the type of path of the container. So if a drive
// is specified, then a table can be created under it and if a table
// is specified, then a row can be created under it. For the sake of
// completeness, if a row is specified, then if the row specified by
// the path does not exist, a new row is created. However, the row
// number may not match as the row numbers only get incremented based
// on the number of rows
if (PathIsDrive(path))
{
if (String.Equals(type, "table", StringComparison.OrdinalIgnoreCase))
{
// Execute command using ODBC connection to create a table
try
{
// create the table using an sql statement
string newTableName = newItemValue.ToString();
if (!TableNameIsValid(newTableName))
{
return;
}
string sql = "create table " + newTableName
+ " (ID INT)";
// Create the table using the Odbc connection from the
// drive.
AccessDBPSDriveInfo di = this.PSDriveInfo as AccessDBPSDriveInfo;
if (di == null)
{
return;
}
OdbcConnection connection = di.Connection;
if (ShouldProcess(newTableName, "create"))
{
OdbcCommand cmd = new OdbcCommand(sql, connection);
cmd.ExecuteScalar();
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "CannotCreateSpecifiedTable",
ErrorCategory.InvalidOperation, path)
);
}
} // if (String...
else if (String.Equals(type, "row", StringComparison.OrdinalIgnoreCase))
{
throw new
ArgumentException("A row cannot be created under a database, specify a path that represents a Table");
}
}// if (PathIsDrive...
else
{
if (String.Equals(type, "table", StringComparison.OrdinalIgnoreCase))
{
if (rowNumber < 0)
{
throw new
ArgumentException("A table cannot be created within another table, specify a path that represents a database");
}
else
{
throw new
ArgumentException("A table cannot be created inside a row, specify a path that represents a database");
}
} //if (String.Equals....
// if path specified is a row, create a new row
else if (String.Equals(type, "row", StringComparison.OrdinalIgnoreCase))
{
// The user is required to specify the values to be inserted
// into the table in a single string separated by commas
string value = newItemValue as string;
if (String.IsNullOrEmpty(value))
{
throw new
ArgumentException("Value argument must have comma separated values of each column in a row");
}
string[] rowValues = value.Split(',');
OdbcDataAdapter da = GetAdapterForTable(tableName);
if (da == null)
{
return;
}
DataSet ds = GetDataSetForTable(da, tableName);
DataTable table = GetDataTable(ds, tableName);
if (rowValues.Length != table.Columns.Count)
{
string message =
String.Format(CultureInfo.CurrentCulture,
"The table has {0} columns and the value specified must have so many comma separated values",
table.Columns.Count);
throw new ArgumentException(message);
}
if (!Force && (rowNumber >=0 && rowNumber < table.Rows.Count))
{
string message = String.Format(CultureInfo.CurrentCulture,
"The row {0} already exists. To create a new row specify row number as {1}, or specify path to a table, or use the -Force parameter",
rowNumber, table.Rows.Count);
throw new ArgumentException(message);
}
if (rowNumber > table.Rows.Count)
{
string message = String.Format(CultureInfo.CurrentCulture,
"To create a new row specify row number as {0}, or specify path to a table",
table.Rows.Count);
throw new ArgumentException(message);
}
// Create a new row and update the row with the input
// provided by the user
DataRow row = table.NewRow();
for (int i = 0; i < rowValues.Length; i++)
{
row[i] = rowValues[i];
}
table.Rows.Add(row);
if (ShouldProcess(tableName, "update rows"))
{
// Update the table from memory back to the data source
da.Update(ds, tableName);
}
}// else if (String...
}// else ...
}
Implementing CopyItem
The System.Management.Automation.Provider.ContainerCmdletProvider.CopyItem copies the specified item to the specified path. The PowerShell engine calls this method when a user calls the Microsoft.PowerShell.Commands.CopyItemCommand cmdlet. This method can also be recursive, copying all of the items children in addition to the item itself.
Similarly to the System.Management.Automation.Provider.ContainerCmdletProvider.NewItem* method, this method performs logic to make sure that the specified item is of the correct type for the path to which it is being copied. For example, if the destination path is a table, the item to be copied must be a row.
protected override void CopyItem(string path, string copyPath, bool recurse)
{
string tableName, copyTableName;
int rowNumber, copyRowNumber;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
PathType copyType = GetNamesFromPath(copyPath, out copyTableName, out copyRowNumber);
if (type == PathType.Invalid)
{
ThrowTerminatingInvalidPathException(path);
}
if (type == PathType.Invalid)
{
ThrowTerminatingInvalidPathException(copyPath);
}
// Get the table and the table to copy to
OdbcDataAdapter da = GetAdapterForTable(tableName);
if (da == null)
{
return;
}
DataSet ds = GetDataSetForTable(da, tableName);
DataTable table = GetDataTable(ds, tableName);
OdbcDataAdapter cda = GetAdapterForTable(copyTableName);
if (cda == null)
{
return;
}
DataSet cds = GetDataSetForTable(cda, copyTableName);
DataTable copyTable = GetDataTable(cds, copyTableName);
// if source represents a table
if (type == PathType.Table)
{
// if copyPath does not represent a table
if (copyType != PathType.Table)
{
ArgumentException e = new ArgumentException("Table can only be copied on to another table location");
WriteError(new ErrorRecord(e, "PathNotValid",
ErrorCategory.InvalidArgument, copyPath));
throw e;
}
// if table already exists then Force parameter should be set
// to force a copy
if (!Force && GetTable(copyTableName) != null)
{
throw new ArgumentException("Specified path already exists");
}
for (int i = 0; i < table.Rows.Count; i++)
{
DataRow row = table.Rows[i];
DataRow copyRow = copyTable.NewRow();
copyRow.ItemArray = row.ItemArray;
copyTable.Rows.Add(copyRow);
}
} // if (type == ...
// if source represents a row
else
{
if (copyType == PathType.Row)
{
if (!Force && (copyRowNumber < copyTable.Rows.Count))
{
throw new ArgumentException("Specified path already exists.");
}
DataRow row = table.Rows[rowNumber];
DataRow copyRow = null;
if (copyRowNumber < copyTable.Rows.Count)
{
// copy to an existing row
copyRow = copyTable.Rows[copyRowNumber];
copyRow.ItemArray = row.ItemArray;
copyRow[0] = GetNextID(copyTable);
}
else if (copyRowNumber == copyTable.Rows.Count)
{
// copy to the next row in the table that will
// be created
copyRow = copyTable.NewRow();
copyRow.ItemArray = row.ItemArray;
copyRow[0] = GetNextID(copyTable);
copyTable.Rows.Add(copyRow);
}
else
{
// attempting to copy to a nonexistent row or a row
// that cannot be created now - throw an exception
string message = String.Format(CultureInfo.CurrentCulture,
"The item cannot be specified to the copied row. Specify row number as {0}, or specify a path to the table.",
table.Rows.Count);
throw new ArgumentException(message);
}
}
else
{
// destination path specified represents a table,
// create a new row and copy the item
DataRow copyRow = copyTable.NewRow();
copyRow.ItemArray = table.Rows[rowNumber].ItemArray;
copyRow[0] = GetNextID(copyTable);
copyTable.Rows.Add(copyRow);
}
}
if (ShouldProcess(copyTableName, "CopyItems"))
{
cda.Update(cds, copyTableName);
}
} //CopyItem
Implementing RemoveItem
The System.Management.Automation.Provider.ContainerCmdletProvider.RemoveItem* method removes the item at the specified path. The PowerShell engine calls this method when a user calls the Microsoft.PowerShell.Commands.RemoveItemCommand cmdlet.
protected override void RemoveItem(string path, bool recurse)
{
string tableName;
int rowNumber = 0;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
if (type == PathType.Table)
{
// if recurse flag has been specified, delete all the rows as well
if (recurse)
{
OdbcDataAdapter da = GetAdapterForTable(tableName);
if (da == null)
{
return;
}
DataSet ds = GetDataSetForTable(da, tableName);
DataTable table = GetDataTable(ds, tableName);
for (int i = 0; i < table.Rows.Count; i++)
{
table.Rows[i].Delete();
}
if (ShouldProcess(path, "RemoveItem"))
{
da.Update(ds, tableName);
RemoveTable(tableName);
}
}//if (recurse...
else
{
// Remove the table
if (ShouldProcess(path, "RemoveItem"))
{
RemoveTable(tableName);
}
}
}
else if (type == PathType.Row)
{
OdbcDataAdapter da = GetAdapterForTable(tableName);
if (da == null)
{
return;
}
DataSet ds = GetDataSetForTable(da, tableName);
DataTable table = GetDataTable(ds, tableName);
table.Rows[rowNumber].Delete();
if (ShouldProcess(path, "RemoveItem"))
{
da.Update(ds, tableName);
}
}
else
{
ThrowTerminatingInvalidPathException(path);
}
}
Next steps
A typical real-world provider is capable of moving items from one path to another within the drive. For an example of a provider that supports moving items, see Writing a navigation provider.