AccessDBProviderSample05

 

This sample shows how to overwrite container methods to support calls to the Move-Item and Join-Path cmdlets. These methods should be implemented when the user needs to move items within a container and if the data store contains nested containers. The provider class in this sample derives from the NavigationCmdletProvider class.

Demonstrates

System_CAPS_importantImportant

Your provider class will most likely derive from one of the following classes and possibly implement other provider interfaces:

For more information about choosing which provider class to derive from based on provider features, see Designing Your Windows PowerShell Provider.

This sample demonstrates the following:

  • Declaring the CmdletProvider attribute.

  • Defining a provider class that derives from the NavigationCmdletProvider class.

  • Overwriting the MoveItem method to change the behavior of the Move-Item cmdlet, allowing the user to move items from one location to another. (This sample does not show how to add dynamic parameters to the Move-Item cmdlet.)

  • Overwriting the MakePath method to change the behavior of the Join-Path cmdlet.

  • Overwriting the IsItemContainer method.

  • Overwriting the GetChildName method.

  • Overwriting the GetParentPath method.

  • Overwriting the NormalizeRelativePath method.

Example

This sample shows how to overwrite the methods needed to move items in a Microsoft Access data base.

namespace Microsoft.Samples.PowerShell.Providers
{
  using System;
  using System.Collections.ObjectModel;
  using System.Data;
  using System.Data.Odbc;
  using System.Diagnostics;
  using System.Globalization;
  using System.IO;
  using System.Management.Automation;
  using System.Management.Automation.Provider;
  using System.Text;
  using System.Text.RegularExpressions;

  #region AccessDBProvider

  /// <summary>
  /// A navigation enabled Windows PowerShell Provider that acts upon 
  /// an Access database. 
  /// </summary>
  /// <remarks>This provider implements drive, item, and navigation 
  /// methods.
  /// </remarks>
  [CmdletProvider("AccessDB", ProviderCapabilities.None)]
  public class AccessDBProvider : NavigationCmdletProvider
  {
    #region Private Properties

    /// <summary>
    /// Characters used to valid table names.
    /// </summary>
    private static string pattern = @"^[a-z]+[0-9]*_*$";

    /// <summary>
    /// The valid path separator character.
    /// </summary>
    private string pathSeparator = "\\";

    /// <summary>
    /// Defines the types of paths to items.
    /// </summary>
    private enum PathType
    {
        /// <summary>
        /// Path to a database.
        /// </summary>
        Database,

        /// <summary>
        /// Path to a table item.
        /// </summary>
        Table,

        /// <summary>
        /// Path to a row item.
        /// </summary>
        Row,

        /// <summary>
        /// A path to an item that is not a database, table, or row.
        /// </summary>
        Invalid
    }

   #endregion Private Properties

    #region Drive Manipulation

    /// <summary>
    /// The Windows PowerShell engine calls this method when the New-Drive 
    /// cmdlet is run. This provider creates a connection to the database 
    /// file and sets the Connection property in the PSDriveInfo.
    /// </summary>
    /// <param name="drive">
    /// Information describing the drive to create.
    /// </param>
    /// <returns>An object that describes the new drive.</returns>
    protected override PSDriveInfo NewDrive(PSDriveInfo drive)
    {
      // Check to see if the supplied drive object is null.
      if (drive == null)
      {
        WriteError(new ErrorRecord(
                                   new ArgumentNullException("drive"),
                                   "NullDrive",
                                   ErrorCategory.InvalidArgument,
                                   null));

        return null;
      }

      // Check to see if the drive root is not null or empty
      // and if it iss 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 the 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;
    } // End NewDrive method.

    /// <summary>
    /// The Windows PowerShell engine calls this method when the 
    /// Remove-Drive cmdlet is run. This provider removes a drive 
    /// from the Access database.
    /// </summary>
    /// <param name="drive">The drive to remove.</param>
    /// <returns>The drive to be removed.</returns>
    protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
    {
      // Check to see if the supplied drive object is null.
      if (drive == null)
      {
        WriteError(new ErrorRecord(
                                   new ArgumentNullException("drive"),
                                   "NullDrive",
                                   ErrorCategory.InvalidArgument,
                                   drive));

        return null;
      }

      // Close the ODBC connection to the drive.
      AccessDBPSDriveInfo accessDBPSDriveInfo = drive as AccessDBPSDriveInfo;

      if (accessDBPSDriveInfo == null)
      {
        return null;
      }

      accessDBPSDriveInfo.Connection.Close();

      return accessDBPSDriveInfo;
    } // End RemoveDrive method.

    #endregion Drive Manipulation

    #region Item Methods

    /// <summary>
    /// The Windows PowerShell engine calls this method when the Get-Item 
    /// cmdlet is run.
    /// </summary>
    /// <param name="path">The path to the item to return.</param>
    protected override void GetItem(string path)
    {
      // Check to see if the supplied path is to a drive.
      if (this.PathIsDrive(path))
      {
        WriteItemObject(this.PSDriveInfo, path, true);
        return;
      } // End if (PathIsDrive...) block.

      // Get the table name and row information from the path and do 
      // the necessary actions.
      string tableName;
      int rowNumber;

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

      if (type == PathType.Table)
      {
        DatabaseTableInfo table = this.GetTable(tableName);
        WriteItemObject(table, path, true);
      }
      else if (type == PathType.Row)
      {
        DatabaseRowInfo row = this.GetRow(tableName, rowNumber);
        WriteItemObject(row, path, false);
      }
      else
      {
        this.ThrowTerminatingInvalidPathException(path);
      }
    } // End GetItem method.

    /// <summary>
    /// The Windows PowerShell engine calls this method when the Set-Item 
    /// cmdlet is run. This provider sets 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 = this.GetNamesFromPath(path, out tableName, out rowNumber);

      if (type != PathType.Row)
      {
        WriteError(new ErrorRecord(
                                   new NotSupportedException("SetNotSupported"), 
                                   string.Empty,
                                   ErrorCategory.InvalidOperation, 
                                   path));

        return;
      }

      // Get in-memory representation of table.
      OdbcDataAdapter da = this.GetAdapterForTable(tableName);

      if (da == null)
      {
        return;
      }

      DataSet ds = this.GetDataSetForTable(da, tableName);
      DataTable table = this.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");
      } // End if (rowNum...) block.

      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);
      }
    } // End SetItem method.

    /// <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 to see if the supplied path is to a drive.
      if (this.PathIsDrive(path))
      {
        return true;
      }

      // Obtain the type, table name, and row number from path.
      string tableName;
      int rowNumber;

      PathType type = this.GetNamesFromPath(path, out tableName, out rowNumber);
      DatabaseTableInfo table = this.GetTable(tableName);
      if (type == PathType.Table)
      {
        // If the path represents a table then the DatabaseTableInfo
        // object for the same should exist.
        if (table != null)
        {
          return true;
        }
      }
      else if (type == PathType.Row)
      {
        // If the path represents a row then DatabaseTableInfo should
        // exist for the table and the specified row number must be within
        // the maximum row count in the table.
        if (table != null && rowNumber < table.RowCount)
        {
          return true;
        }
      }

      return false;
    } // End ItemExists method.

    /// <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 to see if the path is null or empty.
      if (String.IsNullOrEmpty(path))
      {
        result = false;
      }

      // Convert all separators in the path to a uniform character.
      path = this.NormalizePath(path);

      // Split the path into individual chunks.
      string[] pathChunks = path.Split(this.pathSeparator.ToCharArray());

      foreach (string pathChunk in pathChunks)
      {
        if (pathChunk.Length == 0)
        {
          result = false;
        }
      }

      return result;
    } // End IsValidPath method.

    #endregion Item Overloads

    #region Container Overloads

    /// <summary>
    /// The Windows PowerShell engine calls this method when the Get-ChildItem 
    /// cmdlet is run. This provider returns either the tables in the database 
    /// or the rows of the table.
    /// </summary>
    /// <param name="path">The path to the parent item.</param>
    /// <param name="recurse">A Boolean value that indicates true to return all 
    /// child items recursively.
    /// </param>
    protected override void GetChildItems(string path, bool recurse)
    {
      // If the path is to a drive then the children in the path are 
      // tables. Hence all tables in the drive will have to be
      // returned
      if (this.PathIsDrive(path))
      {
        foreach (DatabaseTableInfo table in this.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 (this.ItemExists(path) && recurse)
          {
            this.GetChildItems(path + this.pathSeparator + table.Name, recurse);
          }
        } // End foreach (DatabaseTableInfo...) block.
      } 
      else
      {
        // Get the table name, row number, and the path type from the
        // path.
        string tableName;
        int rowNumber;

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

        if (type == PathType.Table)
        {
          // Obtain all the rows within the table.
          foreach (DatabaseRowInfo row in this.GetRows(tableName))
          {
            WriteItemObject(
                            row, 
                            path + this.pathSeparator + row.RowNumber,
                            false);
          } // End foreach (DatabaseRowInfo...) block.
        }
        else if (type == PathType.Row)
        {
          // In this case the user has directly specified a row, hence
          // just give that particular row.
          DatabaseRowInfo row = this.GetRow(tableName, rowNumber);
          WriteItemObject(
                          row, 
                          path + this.pathSeparator + row.RowNumber,
                          false);
        }
        else
        {
          // The path is not valid so throw an exception.
          this.ThrowTerminatingInvalidPathException(path);
        }
      } // End else block.
    } // End GetChildItems method.

    /// <summary>
    /// Return the names of all child items.
    /// </summary>
    /// <param name="path">The root path.</param>
    /// <param name="returnContainers">This parameter is not used.</param>
    protected override void GetChildNames(
                                          string path,
                                          ReturnContainers returnContainers)
    {
      // If the path is a drive, then the child items are tables.
      // Get the names of all the tables in the drive.
      if (this.PathIsDrive(path))
      {
        foreach (DatabaseTableInfo table in this.GetTables())
        {
          WriteItemObject(table.Name, path, true);
        } 
      } 
      else
      {
        // Get type, table name and row number from the path.
        string tableName;
        int rowNumber;

        PathType type = this.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 this.GetRows(tableName))
          {
            WriteItemObject(row.RowNumber, path, false);
          } // End foreach (DatabaseRowInfo...) block.
        }
        else if (type == PathType.Row)
        {
          // In this case the user has directly specified a row, hence
          // just give that particular row.
          DatabaseRowInfo row = this.GetRow(tableName, rowNumber);

          WriteItemObject(row.RowNumber, path, false);
        }
        else
        {
          this.ThrowTerminatingInvalidPathException(path);
        }
      } // End else block.
    } // End GetChildNames method.

    /// <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 (this.PathIsDrive(path))
      {
        return true;
      }

      return this.ChunkPath(path).Length == 1;
    } // End HasChildItems method.

    /// <summary>
    /// The Windows PowerShell engine calls this method when the New-Item 
    /// cmdlet is run. This method creates a new item at the specified path.
    /// </summary>
    /// <param name="path">The path to the new item.</param>
    /// <param name="type">The type of object to create. A "Table" object 
    /// for creating a new table and a "Row" object for creating a new row 
    /// in a table.
    /// </param>
    /// <param name="newItemValue">
    /// Object for creating a new instance 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 = this.GetNamesFromPath(path, out tableName, out rowNumber);

      if (pt == PathType.Invalid)
      {
        this.ThrowTerminatingInvalidPathException(path);
      }

      // Check to see 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 (this.PathIsDrive(path))
      {
        if (String.Equals(type, "table", StringComparison.OrdinalIgnoreCase))
        {
          // Execute command using the 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));
          }
        } 
        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");
        }
      } 
      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");
          }
        }
        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 = this.GetAdapterForTable(tableName);

          if (da == null)
          {
            return;
          }

          DataSet ds = this.GetDataSetForTable(da, tableName);
          DataTable table = this.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);
          }
        } // End else if (String...) block.
      } // End else block.
    } // End NewItem method.

    /// <summary>
    /// The Windows PowerShell engine calls this method when the Copy-Item 
    /// cmdlet is run. This method copies an item at the specified path to 
    /// the location specified.
    /// </summary>
    /// <param name="path">
    /// Path to the item to copy.
    /// </param>
    /// <param name="copyPath">
    /// Path to 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 = this.GetNamesFromPath(path, out tableName, out rowNumber);
      PathType copyType = this.GetNamesFromPath(copyPath, out copyTableName, out copyRowNumber);

      if (type == PathType.Invalid)
      {
        this.ThrowTerminatingInvalidPathException(path);
      }

      if (type == PathType.Invalid)
      {
        this.ThrowTerminatingInvalidPathException(copyPath);
      }

      // Get the table and the table to copy to. 
      OdbcDataAdapter da = this.GetAdapterForTable(tableName);
      if (da == null)
      {
        return;
      }

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

      OdbcDataAdapter cda = this.GetAdapterForTable(copyTableName);
      if (cda == null)
      {
        return;
      }

      DataSet cds = this.GetDataSetForTable(cda, copyTableName);
      DataTable copyTable = this.GetDataTable(cds, copyTableName);

      // If the 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 a table already exists then the force parameter should be set 
        // to force a copy.
        if (!Force && this.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);
        }
      }
      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] = this.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] = this.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
        {
          // The destination path represents a table, create
          // a new row and copy the item to the new row.
          DataRow copyRow = copyTable.NewRow();
          copyRow.ItemArray = table.Rows[rowNumber].ItemArray;
          copyRow[0] = this.GetNextID(copyTable);
          copyTable.Rows.Add(copyRow);
        }
      }

      if (ShouldProcess(copyTableName, "CopyItems"))
      {
        cda.Update(cds, copyTableName);
      }
    } // End CopyItem method.

    /// <summary>
    /// The Windows PowerShell engine calls this method when the Remove-Item 
    /// cmdlet is run. This method 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 = this.GetNamesFromPath(path, out tableName, out rowNumber);

      if (type == PathType.Table)
      {
        // If the recurse flag has been specified, delete all the rows as well.
        if (recurse)
        {
          OdbcDataAdapter da = this.GetAdapterForTable(tableName);
          if (da == null)
          {
            return;
          }

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

          for (int i = 0; i < table.Rows.Count; i++)
          {
            table.Rows[i].Delete();
          }

          if (ShouldProcess(path, "RemoveItem"))
          {
            da.Update(ds, tableName);
            this.RemoveTable(tableName);
          }
        } 
        else
        {
          // Remove the table.
          if (ShouldProcess(path, "RemoveItem"))
          {
            this.RemoveTable(tableName);
          }
        }
      }
      else if (type == PathType.Row)
      {
        OdbcDataAdapter da = this.GetAdapterForTable(tableName);
        if (da == null)
        {
          return;
        }

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

        table.Rows[rowNumber].Delete();

        if (ShouldProcess(path, "RemoveItem"))
        {
          da.Update(ds, tableName);
        }
      }
      else
      {
        this.ThrowTerminatingInvalidPathException(path);
      }
    } // End RemoveItem method.

    #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 (this.PathIsDrive(path)) 
      { 
        return true; 
      }

      string[] pathChunks = this.ChunkPath(path);
      string tableName;
      int rowNumber;

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

      if (type == PathType.Table)
      {
        foreach (DatabaseTableInfo ti in this.GetTables())
        {
          if (string.Equals(ti.Name, tableName, StringComparison.OrdinalIgnoreCase))
          {
            return true;
          }
        } // End foreach (DatabaseTableInfo...) block.
      } // End if (pathChunks...) block.

      return false;
    } // End IsItemContainer block.

    /// <summary>
    /// Gets 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 (this.PathIsDrive(path))
      {
        return path;
      }

      string tableName;
      int rowNumber;

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

      if (type == PathType.Table)
      {
        return tableName;
      }
      else if (type == PathType.Row)
      {
        return rowNumber.ToString(CultureInfo.CurrentCulture);
      }
      else
      {
        this.ThrowTerminatingInvalidPathException(path);
      }

      return null;
    }

    /// <summary>
    /// Returns the parent portion of the path, removing the child 
    /// segment of the path. 
    /// </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>The parent portion of the path.</returns>
    protected override string GetParentPath(string path, string root)
    {
      // If the 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(this.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 contains 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 = this.NormalizePath(parent);
      normalParent = this.RemoveDriveFromPath(normalParent);
      string normalChild = this.NormalizePath(child);
      normalChild = this.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(this.pathSeparator, StringComparison.OrdinalIgnoreCase))
        {
          result = normalParent;
        }
        else
        {
          result = normalParent + this.pathSeparator;
        }
      } 
      else
      {
        if (!normalParent.Equals(String.Empty) &&
            !normalParent.EndsWith(this.pathSeparator, StringComparison.OrdinalIgnoreCase))
        {
          result = normalParent + this.pathSeparator;
        }
        else
        {
          result = normalParent;
        }

        if (normalChild.StartsWith(this.pathSeparator, StringComparison.OrdinalIgnoreCase))
        {
          result += normalChild.Substring(1);
        }
        else
        {
          result += normalChild;
        }
      } // End else block.

      return result;
    } // End MakePath method.

    /// <summary>
    /// Normalizes the path so that it is 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 = this.NormalizePath(path);
      normalPath = this.RemoveDriveFromPath(normalPath);
      string normalBasePath = this.NormalizePath(basepath);
      normalBasePath = this.RemoveDriveFromPath(normalBasePath);

      if (String.IsNullOrEmpty(normalBasePath))
      {
        return normalPath;
      }
      else
      {
        if (!normalPath.Contains(normalBasePath))
        {
          return null;
        }

        return normalPath.Substring(normalBasePath.Length + this.pathSeparator.Length);
      }
    }

    /// <summary>
    /// The Windows PowerShell engine calls this method when the Move-Item 
    /// cmdlet is run. This provider 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 the path type, table name, and rowNumber from the path.
      string tableName, destTableName;
      int rowNumber, destRowNumber;

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

      PathType destType = this.GetNamesFromPath(
                                           destination, 
                                           out destTableName,
                                           out destRowNumber);

      if (type == PathType.Invalid)
      {
        this.ThrowTerminatingInvalidPathException(path);
      }

      if (destType == PathType.Invalid)
      {
        this.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 = this.GetAdapterForTable(tableName);
        if (da == null)
        {
          return;
        }

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

        OdbcDataAdapter dda = this.GetAdapterForTable(destTableName);
        if (dda == null)
        {
          return;
        }

        DataSet dds = this.GetDataSetForTable(dda, destTableName);
        DataTable destTable = this.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 to see if a given path is actually a drive name.
    /// </summary>
    /// <param name="path">The path to investigate.</param>
    /// <returns>
    /// True if the path represents a drive; otherwise false is returned.
    /// </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 it is
      // just a drive then there will not be any path separators.
      if (String.IsNullOrEmpty(path.Replace(this.PSDriveInfo.Root, string.Empty)) ||
          String.IsNullOrEmpty(path.Replace(this.PSDriveInfo.Root + this.pathSeparator, string.Empty)))
      {
        return true;
      }
      else
      {
        return false;
      }
    } // End PathIsDrive method.

    /// <summary>
    /// Separates 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 separating.
      string normalPath = this.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 + this.pathSeparator, 
                                              string.Empty);

      return pathNoDrive.Split(this.pathSeparator.ToCharArray());
    } // End ChunkPath method.

    /// <summary>
    /// Adapts the path, making sure the correct path separator
    /// character is used.
    /// </summary>
    /// <param name="path">Path to normalize.</param>
    /// <returns>Normalized path.</returns>
    private string NormalizePath(string path)
    {
      string result = path;

      if (!String.IsNullOrEmpty(path))
      {
        result = path.Replace("/", this.pathSeparator);
      }

      return result;
    } // End NormalizePath method.

    /// <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>
    /// Returns the table name and the row number from the path.
    /// </summary>
    /// <param name="path">Path to investigate.</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 to see if the path is a drive.
      if (this.PathIsDrive(path))
      {
        return PathType.Database;
      }

      // Separate the path into parts.
      string[] pathChunks = this.ChunkPath(path);

      switch (pathChunks.Length)
      {
        case 1:
              {
                string name = pathChunks[0];

                if (this.TableNameIsValid(name))
                {
                  tableName = name;
                  retVal = PathType.Table;
                }
              }

              break;

        case 2:
              {
                string name = pathChunks[0];

                if (this.TableNameIsValid(name))
                {
                  tableName = name;
                }

                int number = this.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;
      } // End switch(pathChunks...) block.

      return retVal;
    } // End GetNamesFromPath method.

    /// <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 the ODBC connection to the database 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 the 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 = (int)command.ExecuteScalar();
        }
        catch 
        {
          count = 0;
        }

        // Create the DatabaseTableInfo object representing the table.
        DatabaseTableInfo table =
                 new DatabaseTableInfo(dr, tableName, count, columns);

        results.Add(table);
      } // End foreach (DataRow...) block.

      return results;
    } // End GetTables method.

    /// <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 the rows in the table and add them to the collection.
      try
      {
        OdbcDataAdapter da = this.GetAdapterForTable(tableName);

        if (da == null)
        {
          return null;
        }

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

        int i = 0;
        foreach (DataRow row in table.Rows)
        {
          results.Add(new DatabaseRowInfo(row, i.ToString(CultureInfo.CurrentCulture)));
          i++;
        } // End foreach (DataRow...) block.
      }
      catch (Exception e)
      {
        WriteError(new ErrorRecord(
                                   e, 
                                   "CannotAccessSpecifiedRows",
                                   ErrorCategory.InvalidOperation, 
                                   tableName));
      }

      return results;
    } // End GetRows method.

    /// <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 this.GetTables())
      {
        if (String.Equals(tableName, table.Name, StringComparison.OrdinalIgnoreCase))
        {
          return table;
        }
      }

      return null;
    } // End GetTable method.

    /// <summary>
    /// Removes the specified table from the database
    /// </summary>
    /// <param name="tableName">Name of the table to remove</param>
    private void RemoveTable(string tableName)
    {
      // Check to see if the tablename is valid and if table is present.
      if (String.IsNullOrEmpty(tableName) || !this.TableNameIsValid(tableName) || !this.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));
      }
    } // End RemoveTable method.

    /// <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 || !this.TableNameIsValid(tableName) || !this.TableIsPresent(tableName))
      {
        return null;
      }

      OdbcConnection connection = di.Connection;

      try
      {
        // Create an ODBC data adapter. This can be used 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 an 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 command 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 
        // columns.
        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 it is not already open.                 
        if (connection.State != ConnectionState.Open)
        {
          connection.Open();
        }
      }
      catch (Exception e)
      {
        WriteError(new ErrorRecord(
                                   e, 
                                   "CannotAccessSpecifiedTable",
                                   ErrorCategory.InvalidOperation, 
                                   tableName));
      }

      return da;
    } // End GetAdapterForTable method.

    /// <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)
    {
      // 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.Fill(ds, tableName);
      ds.Locale = CultureInfo.InvariantCulture;

      return ds;
    } // End GetDataSetForTable method.

    /// <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)
    {
      DataTable table = ds.Tables[tableName];
      table.Locale = CultureInfo.InvariantCulture;

      return table;
    } // End GetDataTable method.

    /// <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 = this.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;
    } // End GetRow method.

    /// <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>
    /// <returns>Integer Row number.</returns>
    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;
    } // End SafeConvertRowNumber method.

    /// <summary>
    /// Checks to see if the table name is valid.
    /// </summary>
    /// <param name="tableName">Table name to validate</param>
    /// <remarks>Helps to check for SQL injection attacks</remarks>
    /// <returns>A Boolean value indicating true if the name is valid.</returns>
    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;
    } // End TableNameIsValid method.

    /// <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 to see 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;
    } // End TableIsPresent method.

    /// <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
  } // End AccessDBProvider class.

    #endregion AccessDBProvider

   #region Helper Classes

  #region DatabaseTableInfo

  /// <summary>
  /// Contains information specific to the database table.
  /// Similar to the DirectoryInfo class.
  /// </summary>
  public class DatabaseTableInfo
  {
    /// <summary>
    /// Information about a row.
    /// </summary>
    private DataRow data;

    /// <summary>
    /// The name of a table.
    /// </summary>
    private string name;

    /// <summary>
    /// The number of rows in a table.
    /// </summary>
    private int rowCount;

    /// <summary>
    /// The column difinition of a table.
    /// </summary>
    private DataColumnCollection columns;

    /// <summary>
    /// Initializes a new instance of the DatabaseTableInfo class.
    /// </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)
    {
      this.Name = name;
      this.Data = row;
      this.RowCount = rowCount;
      this.Columns = columns;
    } // End DatabaseTableInfo constructor.

    /// <summary>
    /// Gets or sets row information from the "tables" schema.
    /// </summary>
    public DataRow Data
    {
      get
      {
        return this.data;
      }

      set
      {
        this.data = value;
      }
    }

    /// <summary>
    /// Gets or sets the name of the table.
    /// </summary>
    public string Name
    {
      get
      {
        return this.name;
      }

      set
      {
        this.name = value;
      }
    }

    /// <summary>Gets or sets the number of rows in the table.
    /// </summary>
    public int RowCount
    {
      get
      {
        return this.rowCount;
      }

      set
      {
        this.rowCount = value;
      }
    }

    /// <summary>
    /// Gets or sets the column definitions for the table.
    /// </summary>
    public DataColumnCollection Columns
    {
      get
      {
        return this.columns;
      }

      set
      {
        this.columns = value;
      }
    }
  } // End DatabaseTableInfo class.

  #endregion DatabaseTableInfo

  #region DatabaseRowInfo

  /// <summary>
  /// Contains information specific to an individual table row.
  /// Analogous to the FileInfo class.
  /// </summary>
  public class DatabaseRowInfo
  {
    /// <summary>
    /// The information about a row.
    /// </summary>
    private DataRow data;

    /// <summary>
    /// The number of a row.
    /// </summary>
    private string rowNumber;

    /// <summary>
    /// Initializes a new instance of the DatabaseRowInfo class.
    /// </summary>
    /// <param name="row">The row information.</param>
    /// <param name="name">The row index.</param>
    public DatabaseRowInfo(DataRow row, string name)
    {
      this.RowNumber = name;
      this.Data = row;
    } // End DatabaseRowInfo constructor.

    /// <summary>
    /// Gets or sets row data information.
    /// </summary>
    public DataRow Data
    {
      get
      {
        return this.data;
      }

      set
      {
        this.data = value;
      }
    }

    /// <summary>
    /// Gets or sets the number of the row.
    /// </summary>
    public string RowNumber
    {
      get
      {
        return this.rowNumber;
      }

      set
      {
        this.rowNumber = value;
      }
    }
  } // End DatabaseRowInfo class.

  #endregion DatabaseRowInfo

  #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
  {
    /// <summary>
    /// Describes the ODBC connection.
    /// </summary>
    private OdbcConnection connection;

    /// <summary>
    /// Initializes a new instance of the AccessDBPSDriveInfo class.
    /// </summary>
    /// <param name="driveInfo">Drive provided by this provider</param>
    public AccessDBPSDriveInfo(PSDriveInfo driveInfo)
          : base(driveInfo)
    {
    }

    /// <summary>
    /// Gets or sets the ODBC connection information.
    /// </summary>
    public OdbcConnection Connection
    {
      get { return this.connection; }
      set { this.connection = value; }
    }
  } // End AccessDBPSDriveInfo class.

  #endregion AccessDBPSDriveInfo

  #endregion Helper Classes
}
Show: