AccessDbProviderSample05 Code Sample
The following code shows the implementation of the Windows PowerShell navigation provider described in Creating a Windows PowerShell Navigation Provider. This provider supports recursive commands, nested containers, and relative paths that allow it to navigate the data store.
Code Sample
using System;
using System.IO;
using System.Data;
using System.Data.Odbc;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Text;
using System.Text.RegularExpressions;
using System.Management.Automation;
using System.Management.Automation.Provider;
using System.ComponentModel;
using System.Globalization;
namespace Microsoft.Samples.PowerShell.Providers
{
#region AccessDBProvider
/// <summary>
/// This example implements the navigation methods.
/// </summary>
[CmdletProvider("AccessDB", ProviderCapabilities.None)]
public class AccessDBProvider : NavigationCmdletProvider
{
#region Drive Manipulation
/// <summary>
/// Create a new drive. Create a connection to the database file and set
/// the Connection property in the PSDriveInfo.
/// </summary>
/// <param name="drive">
/// Information describing the drive to add.
/// </param>
/// <returns>The added drive.</returns>
protected override PSDriveInfo NewDrive(PSDriveInfo drive)
{
// check if drive object is null
if (drive == null)
{
WriteError(new ErrorRecord(
new ArgumentNullException("drive"),
"NullDrive",
ErrorCategory.InvalidArgument,
null)
);
return null;
}
// check if drive root is not null or empty
// and if its an existing file
if (String.IsNullOrEmpty(drive.Root) || (File.Exists(drive.Root) == false))
{
WriteError(new ErrorRecord(
new ArgumentException("drive.Root"),
"NoRoot",
ErrorCategory.InvalidArgument,
drive)
);
return null;
}
// create a new drive and create an ODBC connection to the new drive
AccessDBPSDriveInfo accessDBPSDriveInfo = new AccessDBPSDriveInfo(drive);
OdbcConnectionStringBuilder builder = new OdbcConnectionStringBuilder();
builder.Driver = "Microsoft Access Driver (*.mdb)";
builder.Add("DBQ", drive.Root);
OdbcConnection conn = new OdbcConnection(builder.ConnectionString);
conn.Open();
accessDBPSDriveInfo.Connection = conn;
return accessDBPSDriveInfo;
} // NewDrive
/// <summary>
/// Removes a drive from the provider.
/// </summary>
/// <param name="drive">The drive to remove.</param>
/// <returns>The drive removed.</returns>
protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
{
// check if drive object is null
if (drive == null)
{
WriteError(new ErrorRecord(
new ArgumentNullException("drive"),
"NullDrive",
ErrorCategory.InvalidArgument,
drive)
);
return null;
}
// close ODBC connection to the drive
AccessDBPSDriveInfo accessDBPSDriveInfo = drive as AccessDBPSDriveInfo;
if (accessDBPSDriveInfo == null)
{
return null;
}
accessDBPSDriveInfo.Connection.Close();
return accessDBPSDriveInfo;
} // RemoveDrive
#endregion Drive Manipulation
#region Item Methods
/// <summary>
/// Retrieves an item using the specified path.
/// </summary>
/// <param name="path">The path to the item to return.</param>
protected override void GetItem(string path)
{
// check if the path represented is a drive
if (PathIsDrive(path))
{
WriteItemObject(this.PSDriveInfo, path, true);
return;
}// if (PathIsDrive...
// Get table name and row information from the path and do
// necessary actions
string tableName;
int rowNumber;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
if (type == PathType.Table)
{
DatabaseTableInfo table = GetTable(tableName);
WriteItemObject(table, path, true);
}
else if (type == PathType.Row)
{
DatabaseRowInfo row = GetRow(tableName, rowNumber);
WriteItemObject(row, path, false);
}
else
{
ThrowTerminatingInvalidPathException(path);
}
} // GetItem
/// <summary>
/// Set the content of a row of data specified by the supplied path
/// parameter.
/// </summary>
/// <param name="path">Specifies the path to the row whose columns
/// will be updated.</param>
/// <param name="values">Comma separated string of values</param>
protected override void SetItem(string path, object values)
{
// Get type, table name and row number from the path specified
string tableName;
int rowNumber;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
if (type != PathType.Row)
{
WriteError(new ErrorRecord(new NotSupportedException(
"SetNotSupported"), "",
ErrorCategory.InvalidOperation, path));
return;
}
// Get in-memory representation of table
OdbcDataAdapter da = GetAdapterForTable(tableName);
if (da == null)
{
return;
}
DataSet ds = GetDataSetForTable(da, tableName);
DataTable table = GetDataTable(ds, tableName);
if (rowNumber >= table.Rows.Count)
{
// The specified row number has to be available. If not
// NewItem has to be used to add a new row
throw new ArgumentException("Row specified is not available");
} // if (rowNum...
string[] colValues = (values as string).Split(',');
// set the specified row
DataRow row = table.Rows[rowNumber];
for (int i = 0; i < colValues.Length; i++)
{
row[i] = colValues[i];
}
// Update the table
if (ShouldProcess(path, "SetItem"))
{
da.Update(ds, tableName);
}
} // SetItem
/// <summary>
/// Test to see if the specified item exists.
/// </summary>
/// <param name="path">The path to the item to verify.</param>
/// <returns>True if the item is found.</returns>
protected override bool ItemExists(string path)
{
// check if the path represented is a drive
if (PathIsDrive(path))
{
return true;
}
// Obtain type, table name and row number from path
string tableName;
int rowNumber;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
DatabaseTableInfo table = GetTable(tableName);
if (type == PathType.Table)
{
// if specified path represents a table then DatabaseTableInfo
// object for the same should exist
if (table != null)
{
return true;
}
}
else if (type == PathType.Row)
{
// if specified path represents a row then DatabaseTableInfo should
// exist for the table and then specified row number must be within
// the maximum row count in the table
if (table != null && rowNumber < table.RowCount)
{
return true;
}
}
return false;
} // ItemExists
/// <summary>
/// Test to see if the specified path is syntactically valid.
/// </summary>
/// <param name="path">The path to validate.</param>
/// <returns>True if the specified path is valid.</returns>
protected override bool IsValidPath(string path)
{
bool result = true;
// check if the path is null or empty
if (String.IsNullOrEmpty(path))
{
result = false;
}
// convert all separators in the path to a uniform one
path = NormalizePath(path);
// split the path into individual chunks
string[] pathChunks = path.Split(pathSeparator.ToCharArray());
foreach (string pathChunk in pathChunks)
{
if (pathChunk.Length == 0)
{
result = false;
}
}
return result;
} // IsValidPath
#endregion Item Overloads
#region Container Overloads
/// <summary>
/// Return either the tables in the database or the datarows
/// </summary>
/// <param name="path">The path to the parent</param>
/// <param name="recurse">True to return all child items recursively.
/// </param>
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
} // GetChildItems
/// <summary>
/// Return the names of all child items.
/// </summary>
/// <param name="path">The root path.</param>
/// <param name="returnContainers">Not used.</param>
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
} // GetChildNames
/// <summary>
/// Determines if the specified path has child items.
/// </summary>
/// <param name="path">The path to examine.</param>
/// <returns>
/// True if the specified path has child items.
/// </returns>
protected override bool HasChildItems(string path)
{
if (PathIsDrive(path))
{
return true;
}
return (ChunkPath(path).Length == 1);
} // HasChildItems
/// <summary>
/// Creates a new item at the specified path.
/// </summary>
///
/// <param name="path">
/// The path to the new item.
/// </param>
///
/// <param name="type">
/// Type for the object to create. "Table" for creating a new table and
/// "Row" for creating a new row in a table.
/// </param>
///
/// <param name="newItemValue">
/// Object for creating new instance of a type at the specified path. For
/// creating a "Table" the object parameter is ignored and for creating
/// a "Row" the object must be of type string which will contain comma
/// separated values of the rows to insert.
/// </param>
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();
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 ...
} // NewItem
/// <summary>
/// Copies an item at the specified path to the location specified
/// </summary>
///
/// <param name="path">
/// Path of the item to copy
/// </param>
///
/// <param name="copyPath">
/// Path of the item to copy to
/// </param>
///
/// <param name="recurse">
/// Tells the provider to recurse subcontainers when copying
/// </param>
///
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
/// <summary>
/// Removes (deletes) the item at the specified path
/// </summary>
///
/// <param name="path">
/// The path to the item to remove.
/// </param>
///
/// <param name="recurse">
/// True if all children in a subtree should be removed, false if only
/// the item at the specified path should be removed. Is applicable
/// only for container (table) items. Its ignored otherwise (even if
/// specified).
/// </param>
///
/// <remarks>
/// There are no elements in this store which are hidden from the user.
/// Hence this method will not check for the presence of the Force
/// parameter
/// </remarks>
///
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);
}
} // RemoveItem
#endregion Container Overloads
#region Navigation
/// <summary>
/// Determine if the path specified is that of a container.
/// </summary>
/// <param name="path">The path to check.</param>
/// <returns>True if the path specifies a container.</returns>
protected override bool IsItemContainer(string path)
{
if (PathIsDrive(path))
{
return true;
}
string[] pathChunks = ChunkPath(path);
string tableName;
int rowNumber;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
if (type == PathType.Table)
{
foreach (DatabaseTableInfo ti in GetTables())
{
if (string.Equals(ti.Name, tableName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
} // foreach (DatabaseTableInfo...
} // if (pathChunks...
return false;
} // IsItemContainer
/// <summary>
/// Get the name of the leaf element in the specified path
/// </summary>
///
/// <param name="path">
/// The full or partial provider specific path
/// </param>
///
/// <returns>
/// The leaf element in the path
/// </returns>
protected override string GetChildName(string path)
{
if (PathIsDrive(path))
{
return path;
}
string tableName;
int rowNumber;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
if (type == PathType.Table)
{
return tableName;
}
else if (type == PathType.Row)
{
return rowNumber.ToString(CultureInfo.CurrentCulture);
}
else
{
ThrowTerminatingInvalidPathException(path);
}
return null;
}
/// <summary>
/// Removes the child segment of the path and returns the remaining
/// parent portion
/// </summary>
///
/// <param name="path">
/// A full or partial provider specific path. The path may be to an
/// item that may or may not exist.
/// </param>
///
/// <param name="root">
/// The fully qualified path to the root of a drive. This parameter
/// may be null or empty if a mounted drive is not in use for this
/// operation. If this parameter is not null or empty the result
/// of the method should not be a path to a container that is a
/// parent or in a different tree than the root.
/// </param>
///
/// <returns></returns>
protected override string GetParentPath(string path, string root)
{
// If root is specified then the path has to contain
// the root. If not nothing should be returned
if (!String.IsNullOrEmpty(root))
{
if (!path.Contains(root))
{
return null;
}
}
return path.Substring(0, path.LastIndexOf(pathSeparator, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Joins two strings with a provider specific path separator.
/// </summary>
///
/// <param name="parent">
/// The parent segment of a path to be joined with the child.
/// </param>
///
/// <param name="child">
/// The child segment of a path to be joined with the parent.
/// </param>
///
/// <returns>
/// A string that represents the parent and child segments of the path
/// joined by a path separator.
/// </returns>
protected override string MakePath(string parent, string child)
{
string result;
string normalParent = NormalizePath(parent);
normalParent = RemoveDriveFromPath(normalParent);
string normalChild = NormalizePath(child);
normalChild = RemoveDriveFromPath(normalChild);
if (String.IsNullOrEmpty(normalParent) && String.IsNullOrEmpty(normalChild))
{
result = String.Empty;
}
else if (String.IsNullOrEmpty(normalParent) && !String.IsNullOrEmpty(normalChild))
{
result = normalChild;
}
else if (!String.IsNullOrEmpty(normalParent) && String.IsNullOrEmpty(normalChild))
{
if (normalParent.EndsWith(pathSeparator, StringComparison.OrdinalIgnoreCase))
{
result = normalParent;
}
else
{
result = normalParent + pathSeparator;
}
} // else if (!String...
else
{
if (!normalParent.Equals(String.Empty) &&
!normalParent.EndsWith(pathSeparator, StringComparison.OrdinalIgnoreCase))
{
result = normalParent + pathSeparator;
}
else
{
result = normalParent;
}
if (normalChild.StartsWith(pathSeparator, StringComparison.OrdinalIgnoreCase))
{
result += normalChild.Substring(1);
}
else
{
result += normalChild;
}
} // else
return result;
} // MakePath
/// <summary>
/// Normalizes the path that was passed in and returns the normalized
/// path as a relative path to the basePath that was passed.
/// </summary>
///
/// <param name="path">
/// A fully qualified provider specific path to an item. The item
/// should exist or the provider should write out an error.
/// </param>
///
/// <param name="basepath">
/// The path that the return value should be relative to.
/// </param>
///
/// <returns>
/// A normalized path that is relative to the basePath that was
/// passed. The provider should parse the path parameter, normalize
/// the path, and then return the normalized path relative to the
/// basePath.
/// </returns>
protected override string NormalizeRelativePath(string path,
string basepath)
{
// Normalize the paths first
string normalPath = NormalizePath(path);
normalPath = RemoveDriveFromPath(normalPath);
string normalBasePath = NormalizePath(basepath);
normalBasePath = RemoveDriveFromPath(normalBasePath);
if (String.IsNullOrEmpty(normalBasePath))
{
return normalPath;
}
else
{
if (!normalPath.Contains(normalBasePath))
{
return null;
}
return normalPath.Substring(normalBasePath.Length + pathSeparator.Length);
}
}
/// <summary>
/// Moves the item specified by the path to the specified destination
/// </summary>
///
/// <param name="path">
/// The path to the item to be moved
/// </param>
///
/// <param name="destination">
/// The path of the destination container
/// </param>
protected override void MoveItem(string path, string destination)
{
// Get type, table name and rowNumber from the path
string tableName, destTableName;
int rowNumber, destRowNumber;
PathType type = GetNamesFromPath(path, out tableName, out rowNumber);
PathType destType = GetNamesFromPath(destination, out destTableName,
out destRowNumber);
if (type == PathType.Invalid)
{
ThrowTerminatingInvalidPathException(path);
}
if (destType == PathType.Invalid)
{
ThrowTerminatingInvalidPathException(destination);
}
if (type == PathType.Table)
{
ArgumentException e = new ArgumentException("Move not supported for tables");
WriteError(new ErrorRecord(e, "MoveNotSupported",
ErrorCategory.InvalidArgument, path));
throw e;
}
else
{
OdbcDataAdapter da = GetAdapterForTable(tableName);
if (da == null)
{
return;
}
DataSet ds = GetDataSetForTable(da, tableName);
DataTable table = GetDataTable(ds, tableName);
OdbcDataAdapter dda = GetAdapterForTable(destTableName);
if (dda == null)
{
return;
}
DataSet dds = GetDataSetForTable(dda, destTableName);
DataTable destTable = GetDataTable(dds, destTableName);
DataRow row = table.Rows[rowNumber];
if (destType == PathType.Table)
{
DataRow destRow = destTable.NewRow();
destRow.ItemArray = row.ItemArray;
}
else
{
DataRow destRow = destTable.Rows[destRowNumber];
destRow.ItemArray = row.ItemArray;
}
// Update the changes
if (ShouldProcess(path, "MoveItem"))
{
WriteItemObject(row, path, false);
dda.Update(dds, destTableName);
}
}
}
#endregion Navigation
#region Helper Methods
/// <summary>
/// Checks if a given path is actually a drive name.
/// </summary>
/// <param name="path">The path to check.</param>
/// <returns>
/// True if the path given represents a drive, false otherwise.
/// </returns>
private bool PathIsDrive(string path)
{
// Remove the drive name and first path separator. If the
// path is reduced to nothing, it is a drive. Also if its
// just a drive then there wont be any path separators
if (String.IsNullOrEmpty(
path.Replace(this.PSDriveInfo.Root, "")) ||
String.IsNullOrEmpty(
path.Replace(this.PSDriveInfo.Root + pathSeparator, ""))
)
{
return true;
}
else
{
return false;
}
} // PathIsDrive
/// <summary>
/// Breaks up the path into individual elements.
/// </summary>
/// <param name="path">The path to split.</param>
/// <returns>An array of path segments.</returns>
private string[] ChunkPath(string path)
{
// Normalize the path before splitting
string normalPath = NormalizePath(path);
// Return the path with the drive name and first path
// separator character removed, split by the path separator.
string pathNoDrive = normalPath.Replace(this.PSDriveInfo.Root
+ pathSeparator, "");
return pathNoDrive.Split(pathSeparator.ToCharArray());
} // ChunkPath
/// <summary>
/// Adapts the path, making sure the correct path separator
/// character is used.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private string NormalizePath(string path)
{
string result = path;
if (!String.IsNullOrEmpty(path))
{
result = path.Replace("/", pathSeparator);
}
return result;
} // NormalizePath
/// <summary>
/// Ensures that the drive is removed from the specified path
/// </summary>
///
/// <param name="path">Path from which drive needs to be removed</param>
/// <returns>Path with drive information removed</returns>
private string RemoveDriveFromPath(string path)
{
string result = path;
string root;
if (this.PSDriveInfo == null)
{
root = String.Empty;
}
else
{
root = this.PSDriveInfo.Root;
}
if (result == null)
{
result = String.Empty;
}
if (result.Contains(root))
{
result = result.Substring(result.IndexOf(root, StringComparison.OrdinalIgnoreCase) + root.Length);
}
return result;
}
/// <summary>
/// Chunks the path and returns the table name and the row number
/// from the path
/// </summary>
/// <param name="path">Path to chunk and obtain information</param>
/// <param name="tableName">Name of the table as represented in the
/// path</param>
/// <param name="rowNumber">Row number obtained from the path</param>
/// <returns>what the path represents</returns>
private PathType GetNamesFromPath(string path, out string tableName, out int rowNumber)
{
PathType retVal = PathType.Invalid;
rowNumber = -1;
tableName = null;
// Check if the path specified is a drive
if (PathIsDrive(path))
{
return PathType.Database;
}
// chunk the path into parts
string[] pathChunks = ChunkPath(path);
switch (pathChunks.Length)
{
case 1:
{
string name = pathChunks[0];
if (TableNameIsValid(name))
{
tableName = name;
retVal = PathType.Table;
}
}
break;
case 2:
{
string name = pathChunks[0];
if (TableNameIsValid(name))
{
tableName = name;
}
int number = SafeConvertRowNumber(pathChunks[1]);
if (number >= 0)
{
rowNumber = number;
retVal = PathType.Row;
}
else
{
WriteError(new ErrorRecord(
new ArgumentException("Row number is not valid"),
"RowNumberNotValid",
ErrorCategory.InvalidArgument,
path));
}
}
break;
default:
{
WriteError(new ErrorRecord(
new ArgumentException("The path supplied has too many segments"),
"PathNotValid",
ErrorCategory.InvalidArgument,
path));
}
break;
} // switch(pathChunks...
return retVal;
} // GetNamesFromPath
/// <summary>
/// Throws an argument exception stating that the specified path does
/// not represent either a table or a row
/// </summary>
/// <param name="path">path which is invalid</param>
private void ThrowTerminatingInvalidPathException(string path)
{
StringBuilder message = new StringBuilder("Path must represent either a table or a row :");
message.Append(path);
throw new ArgumentException(message.ToString());
}
/// <summary>
/// Retrieve the list of tables from the database.
/// </summary>
/// <returns>
/// Collection of DatabaseTableInfo objects, each object representing
/// information about one database table
/// </returns>
private Collection<DatabaseTableInfo> GetTables()
{
Collection<DatabaseTableInfo> results =
new Collection<DatabaseTableInfo>();
// using ODBC connection to the database and get the schema of tables
AccessDBPSDriveInfo di = this.PSDriveInfo as AccessDBPSDriveInfo;
if (di == null)
{
return null;
}
OdbcConnection connection = di.Connection;
DataTable dt = connection.GetSchema("Tables");
int count;
// iterate through all rows in the schema and create DatabaseTableInfo
// objects which represents a table
foreach (DataRow dr in dt.Rows)
{
String tableName = dr["TABLE_NAME"] as String;
DataColumnCollection columns = null;
// find the number of rows in the table
try
{
String cmd = "Select count(*) from \"" + tableName + "\"";
OdbcCommand command = new OdbcCommand(cmd, connection);
count = (Int32)command.ExecuteScalar();
}
catch
{
count = 0;
}
// create DatabaseTableInfo object representing the table
DatabaseTableInfo table =
new DatabaseTableInfo(dr, tableName, count, columns);
results.Add(table);
} // foreach (DataRow...
return results;
} // GetTables
/// <summary>
/// Return row information from a specified table.
/// </summary>
/// <param name="tableName">The name of the database table from
/// which to retrieve rows.</param>
/// <returns>Collection of row information objects.</returns>
private Collection<DatabaseRowInfo> GetRows(string tableName)
{
Collection<DatabaseRowInfo> results =
new Collection<DatabaseRowInfo>();
// Obtain rows in the table and add it to the collection
try
{
OdbcDataAdapter da = GetAdapterForTable(tableName);
if (da == null)
{
return null;
}
DataSet ds = GetDataSetForTable(da, tableName);
DataTable table = GetDataTable(ds, tableName);
int i = 0;
foreach (DataRow row in table.Rows)
{
results.Add(new DatabaseRowInfo(row, i.ToString(CultureInfo.CurrentCulture)));
i++;
} // foreach (DataRow...
}
catch (Exception e)
{
WriteError(new ErrorRecord(e, "CannotAccessSpecifiedRows",
ErrorCategory.InvalidOperation, tableName));
}
return results;
} // GetRows
/// <summary>
/// Retrieve information about a single table.
/// </summary>
/// <param name="tableName">The table for which to retrieve
/// data.</param>
/// <returns>Table information.</returns>
private DatabaseTableInfo GetTable(string tableName)
{
foreach (DatabaseTableInfo table in GetTables())
{
if (String.Equals(tableName, table.Name, StringComparison.OrdinalIgnoreCase))
{
return table;
}
}
return null;
} // GetTable
/// <summary>
/// Removes the specified table from the database
/// </summary>
/// <param name="tableName">Name of the table to remove</param>
private void RemoveTable(string tableName)
{
// validate if tablename is valid and if table is present
if (String.IsNullOrEmpty(tableName) || !TableNameIsValid(tableName) || !TableIsPresent(tableName))
{
return;
}
// Execute command using ODBC connection to remove a table
try
{
// delete the table using an sql statement
string sql = "drop table " + tableName;
AccessDBPSDriveInfo di = this.PSDriveInfo as AccessDBPSDriveInfo;
if (di == null)
{
return;
}
OdbcConnection connection = di.Connection;
OdbcCommand cmd = new OdbcCommand(sql, connection);
cmd.ExecuteScalar();
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "CannotRemoveSpecifiedTable",
ErrorCategory.InvalidOperation, null)
);
}
} // RemoveTable
/// <summary>
/// Obtain a data adapter for the specified Table
/// </summary>
/// <param name="tableName">Name of the table to obtain the
/// adapter for</param>
/// <returns>Adapter object for the specified table</returns>
/// <remarks>An adapter serves as a bridge between a DataSet (in memory
/// representation of table) and the data source</remarks>
private OdbcDataAdapter GetAdapterForTable(string tableName)
{
OdbcDataAdapter da = null;
AccessDBPSDriveInfo di = this.PSDriveInfo as AccessDBPSDriveInfo;
if (di == null || !TableNameIsValid(tableName) || !TableIsPresent(tableName))
{
return null;
}
OdbcConnection connection = di.Connection;
try
{
// Create a odbc data adpater. This can be sued to update the
// data source with the records that will be created here
// using data sets
string sql = "Select * from " + tableName;
da = new OdbcDataAdapter(new OdbcCommand(sql, connection));
// Create a odbc command builder object. This will create sql
// commands automatically for a single table, thus
// eliminating the need to create new sql statements for
// every operation to be done.
OdbcCommandBuilder cmd = new OdbcCommandBuilder(da);
// Set the delete cmd for the table here
sql = "Delete from " + tableName + " where ID = ?";
da.DeleteCommand = new OdbcCommand(sql, connection);
// Specify a DeleteCommand parameter based on the "ID"
// column
da.DeleteCommand.Parameters.Add(new OdbcParameter());
da.DeleteCommand.Parameters[0].SourceColumn = "ID";
// Create an InsertCommand based on the sql string
// Insert into "tablename" values (?,?,?)" where
// ? represents a column in the table. Note that
// the number of ? will be equal to the number of
// columnds
DataSet ds = new DataSet();
da.FillSchema(ds, SchemaType.Source);
ds.Locale = CultureInfo.InvariantCulture;
sql = "Insert into " + tableName + " values ( ";
for (int i = 0; i < ds.Tables["Table"].Columns.Count; i++)
{
sql += "?, ";
}
sql = sql.Substring(0, sql.Length - 2);
sql += ")";
da.InsertCommand = new OdbcCommand(sql, connection);
// Create parameters for the InsertCommand based on the
// captions of each column
for (int i = 0; i < ds.Tables["Table"].Columns.Count; i++)
{
da.InsertCommand.Parameters.Add(new OdbcParameter());
da.InsertCommand.Parameters[i].SourceColumn =
ds.Tables["Table"].Columns[i].Caption;
}
// Open the connection if its not already open
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
}
catch (Exception e)
{
WriteError(new ErrorRecord(e, "CannotAccessSpecifiedTable",
ErrorCategory.InvalidOperation, tableName));
}
return da;
} // GetAdapterForTable
/// <summary>
/// Gets the DataSet (in memory representation) for the table
/// for the specified adapter
/// </summary>
/// <param name="adapter">Adapter to be used for obtaining
/// the table</param>
/// <param name="tableName">Name of the table for which a
/// DataSet is required</param>
/// <returns>The DataSet with the filled in schema</returns>
private DataSet GetDataSetForTable(OdbcDataAdapter adapter, string tableName)
{
Debug.Assert(adapter != null);
// Create a dataset object which will provide an in-memory
// representation of the data being worked upon in the
// data source.
DataSet ds = new DataSet();
// Create a table named "Table" which will contain the same
// schema as in the data source.
//adapter.FillSchema(ds, SchemaType.Source);
adapter.Fill(ds, tableName);
ds.Locale = CultureInfo.InvariantCulture;
return ds;
} //GetDataSetForTable
/// <summary>
/// Get the DataTable object which can be used to operate on
/// for the specified table in the data source
/// </summary>
/// <param name="ds">DataSet object which contains the tables
/// schema</param>
/// <param name="tableName">Name of the table</param>
/// <returns>Corresponding DataTable object representing
/// the table</returns>
///
private DataTable GetDataTable(DataSet ds, string tableName)
{
Debug.Assert(ds != null);
Debug.Assert(tableName != null);
DataTable table = ds.Tables[tableName];
table.Locale = CultureInfo.InvariantCulture;
return table;
} // GetDataTable
/// <summary>
/// Retrieves a single row from the named table.
/// </summary>
/// <param name="tableName">The table that contains the
/// numbered row.</param>
/// <param name="row">The index of the row to return.</param>
/// <returns>The specified table row.</returns>
private DatabaseRowInfo GetRow(string tableName, int row)
{
Collection<DatabaseRowInfo> di = GetRows(tableName);
// if the row is invalid write an appropriate error else return the
// corresponding row information
if (row < di.Count && row >= 0)
{
return di[row];
}
else
{
WriteError(new ErrorRecord(
new ItemNotFoundException(),
"RowNotFound",
ErrorCategory.ObjectNotFound,
row.ToString(CultureInfo.CurrentCulture))
);
}
return null;
} // GetRow
/// <summary>
/// Method to safely convert a string representation of a row number
/// into its Int32 equivalent
/// </summary>
/// <param name="rowNumberAsStr">String representation of the row
/// number</param>
/// <remarks>If there is an exception, -1 is returned</remarks>
private int SafeConvertRowNumber(string rowNumberAsStr)
{
int rowNumber = -1;
try
{
rowNumber = Convert.ToInt32(rowNumberAsStr, CultureInfo.CurrentCulture);
}
catch (FormatException fe)
{
WriteError(new ErrorRecord(fe, "RowStringFormatNotValid",
ErrorCategory.InvalidData, rowNumberAsStr));
}
catch (OverflowException oe)
{
WriteError(new ErrorRecord(oe, "RowStringConversionToNumberFailed",
ErrorCategory.InvalidData, rowNumberAsStr));
}
return rowNumber;
} // SafeConvertRowNumber
/// <summary>
/// Check if a table name is valid
/// </summary>
/// <param name="tableName">Table name to validate</param>
/// <remarks>Helps to check for SQL injection attacks</remarks>
private bool TableNameIsValid(string tableName)
{
Regex exp = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (exp.IsMatch(tableName))
{
return true;
}
WriteError(new ErrorRecord(
new ArgumentException("Table name not valid"), "TableNameNotValid",
ErrorCategory.InvalidArgument, tableName));
return false;
} // TableNameIsValid
/// <summary>
/// Checks to see if the specified table is present in the
/// database
/// </summary>
/// <param name="tableName">Name of the table to check</param>
/// <returns>true, if table is present, false otherwise</returns>
private bool TableIsPresent(string tableName)
{
// using ODBC connection to the database and get the schema of tables
AccessDBPSDriveInfo di = this.PSDriveInfo as AccessDBPSDriveInfo;
if (di == null)
{
return false;
}
OdbcConnection connection = di.Connection;
DataTable dt = connection.GetSchema("Tables");
// check if the specified tableName is available
// in the list of tables present in the database
foreach (DataRow dr in dt.Rows)
{
string name = dr["TABLE_NAME"] as string;
if (name.Equals(tableName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
WriteError(new ErrorRecord(
new ArgumentException("Specified Table is not present in database"), "TableNotAvailable",
ErrorCategory.InvalidArgument, tableName));
return false;
}// TableIsPresent
/// <summary>
/// Gets the next available ID in the table
/// </summary>
/// <param name="table">DataTable object representing the table to
/// search for ID</param>
/// <returns>next available id</returns>
private int GetNextID(DataTable table)
{
int big = 0;
for (int i = 0; i < table.Rows.Count; i++)
{
DataRow row = table.Rows[i];
int id = (int)row["ID"];
if (big < id)
{
big = id;
}
}
big++;
return big;
}
#endregion Helper Methods
#region Private Properties
private string pathSeparator = "\\";
private static string pattern = @"^[a-z]+[0-9]*_*$";
private enum PathType { Database, Table, Row, Invalid };
#endregion Private Properties
} // AccessDBProvider
#endregion AccessDBProvider
#region Helper Classes
#region AccessDBPSDriveInfo
/// <summary>
/// Any state associated with the drive should be held here.
/// In this case, it's the connection to the database.
/// </summary>
internal class AccessDBPSDriveInfo : PSDriveInfo
{
private OdbcConnection connection;
/// <summary>
/// ODBC connection information.
/// </summary>
public OdbcConnection Connection
{
get { return connection; }
set { connection = value; }
}
/// <summary>
/// Constructor that takes one argument
/// </summary>
/// <param name="driveInfo">Drive provided by this provider</param>
public AccessDBPSDriveInfo(PSDriveInfo driveInfo)
: base(driveInfo)
{ }
} // class AccessDBPSDriveInfo
#endregion AccessDBPSDriveInfo
#region DatabaseTableInfo
/// <summary>
/// Contains information specific to the database table.
/// Similar to the DirectoryInfo class.
/// </summary>
public class DatabaseTableInfo
{
/// <summary>
/// Row from the "tables" schema
/// </summary>
public DataRow Data
{
get
{
return data;
}
set
{
data = value;
}
}
private DataRow data;
/// <summary>
/// The table name.
/// </summary>
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
private String name;
/// <summary>
/// The number of rows in the table.
/// </summary>
public int RowCount
{
get
{
return rowCount;
}
set
{
rowCount = value;
}
}
private int rowCount;
/// <summary>
/// The column definitions for the table.
/// </summary>
public DataColumnCollection Columns
{
get
{
return columns;
}
set
{
columns = value;
}
}
private DataColumnCollection columns;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="row">The row definition.</param>
/// <param name="name">The table name.</param>
/// <param name="rowCount">The number of rows in the table.</param>
/// <param name="columns">Information on the column tables.</param>
public DatabaseTableInfo(DataRow row, string name, int rowCount,
DataColumnCollection columns)
{
Name = name;
Data = row;
RowCount = rowCount;
Columns = columns;
} // DatabaseTableInfo
} // class DatabaseTableInfo
#endregion DatabaseTableInfo
#region DatabaseRowInfo
/// <summary>
/// Contains information specific to an individual table row.
/// Analogous to the FileInfo class.
/// </summary>
public class DatabaseRowInfo
{
/// <summary>
/// Row data information.
/// </summary>
public DataRow Data
{
get
{
return data;
}
set
{
data = value;
}
}
private DataRow data;
/// <summary>
/// The row index.
/// </summary>
public string RowNumber
{
get
{
return rowNumber;
}
set
{
rowNumber = value;
}
}
private string rowNumber;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="row">The row information.</param>
/// <param name="name">The row index.</param>
public DatabaseRowInfo(DataRow row, string name)
{
RowNumber = name;
Data = row;
} // DatabaseRowInfo
} // class DatabaseRowInfo
#endregion DatabaseRowInfo
#endregion Helper Classes
}
See Also
Collaborate with us on GitHub
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, see our contributor guide.