创建 Windows PowerShell 内容提供者

本主题描述了如何创建一个 Windows PowerShell 提供者,使用户能够作数据存储中项目的内容。 因此,能够作项目内容的提供者被称为Windows PowerShell内容提供者。

注释

您可以使用 Windows Vista 和 .NET Framework 3.0 运行时组件Microsoft Windows 软件开发工具包下载该提供者的 C# 源文件(AccessDBSampleProvider06.cs)。 有关下载说明,请参见 《如何安装Windows PowerShell》和《Windows PowerShell SDK》。 下载的源文件可在 <PowerShell Samples> 目录中获取。 有关其他 Windows PowerShell 提供者实现的更多信息,请参见 “设计你的 Windows PowerShell 提供者”。

定义 Windows PowerShell 内容提供者类

Windows PowerShell 内容提供者必须创建一个支持 System.Management.Automation.Provider.IContentCmdletProvider 接口的 .NET 类。 以下是本节描述的项目提供者的类定义。

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

注意,在本类定义中, System.Management.Automation.Provider.CmdletProviderAttribute 属性包含两个参数。 第一个参数指定了 Windows PowerShell 所用提供者的用户友好名称。 第二个参数指定了 Windows PowerShell 在命令处理过程中向 Windows PowerShell 运行时暴露的具体功能。 对于该提供者,没有额外的 Windows PowerShell 特定功能。

定义基类的功能

如《 设计你的 Windows PowerShell 提供者》中所述, System.Management.Automation.Provider.NavigationCmdletProvider 类源自其他提供不同提供者功能的类。 因此,Windows PowerShell 内容提供者通常定义了这些类提供的所有功能。

关于如何实现会话特定初始化信息和释放提供者使用的资源的功能,请参见 创建基础 Windows PowerShell 提供者。 然而,大多数提供者,包括这里描述的提供者,都可以使用 Windows PowerShell 提供的默认实现。

要访问数据存储,提供者必须实现 System.Management.Automation.Provider.DriveCmdletProvider 基类的方法。 关于实现这些方法的更多信息,请参见 创建 Windows PowerShell 驱动器提供者

为了作数据存储中的项目,如获取、设置和清除项目,提供者必须实现 System.Management.Automation.Provider.ItemCmdletProvider 基类提供的方法。 关于实现这些方法的更多信息,请参见 创建 Windows PowerShell 项提供者

为了处理多层数据存储,提供者必须实现 System.Management.Automation.Provider.ContainerCmdletProvider 基类所提供的方法。 有关实现这些方法的更多信息,请参见 “创建 Windows PowerShell 容器提供者”。

为了支持递归命令、嵌套容器和相对路径,提供者必须实现 System.Management.Automation.Provider.NavigationCmdletProvider 基类。 此外,该 Windows PowerShell 内容提供者可以将 System.Management.Automation.Provider.IContentCmdletProvider 接口附加到 System.Management.Automation.Provider.NavigationCmdletProvider 基类,因此必须实现该类提供的方法。 欲了解更多信息,请参见实现这些方法,参见 实现导航 Windows PowerShell 提供者

实施内容阅读器

要读取项目内容,提供者必须实现源自 System.Management.Automation.Provider.IContentReader 的内容阅读类。 该提供者的内容阅读器允许访问数据表中某行的内容。 内容阅读类定义了读取方法,用于从指定行检索数据并返回表示该数据的列表;一个Seek方法,用于移动内容阅读器;一个关闭方法,关闭阅读器;以及一个Dispose方法。

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

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

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

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

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

        int rowsRead = 0;

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

        return results;
    } // Read

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

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

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

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

            int numRows = rows.Count;

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

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

    } // Seek

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

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

实施内容写作

要为项目编写内容,提供者必须实现源自 System.Management.Automation.Provider.IContentWriter 的内容编写类。 内容写作者类 定义了写 入方法(写入指定行内容)、 Seeek 方法(移动内容写作者)、关闭内容写作者的 Close 方法,以及 Dispose 方法。

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

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

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

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

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

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

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

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

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

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

        return null;
    } // Write

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

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

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

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

        int numRows = rows.Count;

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

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

    } // Seek

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

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

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

获取内容阅读器

要从项目中获取内容,提供者必须实现 System.Management.Automation.Provider.IContentCmdletProvider.GetContentReader* 以支持该 Get-Content 命令。 该方法返回位于指定路径的项目内容阅读器。 然后可以打开阅读对象来读取内容。

这是该提供者该方法中 System.Management.Automation.Provider.IContentCmdletProvider.GetContentReader* 的实现。

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

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

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

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

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

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

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

关于实施GetContentReader需要注意的事项

以下条件可能适用于 System.Management.Automation.Provider.IContentCmdletProvider.GetContentReader*的实现:

将动态参数附加到 Get-Content cmdlet

Get-Content该 cmdlet 可能需要在运行时动态指定额外参数。 为了提供这些动态参数,Windows PowerShell 内容提供者必须实现 System.Management.Automation.Provider.IContentCmdletProvider.GetContentReaderdynamicparameters* 方法。 该方法检索指定路径上的动态参数,返回一个具有类似 cmdlet 类或 System.Management.Automation.RuntimeDefinedParameterDictionary 对象的属性和字段的对象。 Windows PowerShell 运行时使用返回的对象将参数添加到 cmdlet。

该 Windows PowerShell 容器提供者未实现此方法。 不过,以下代码是该方法的默认实现。

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

检索内容撰稿人

要为项目写入内容,提供者必须实现 System.Management.Automation.Provider.IContentCmdletProvider.GetContentWriter* 以支持 Set-ContentAdd-Content 命令列表。 该方法返回位于指定路径的项目的内容编写者。

这是 System.Management.Automation.Provider.IContentCmdletProvider.GetContentWriter* 的实现。

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

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

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

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

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

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

    return new AccessDBContentWriter(path, this);
}

关于实施 GetContentWriter 需要记住的事项

以下条件可能适用于您实现 的System.Management.Automation.Provider.IContentCmdletProvider.GetContentWriter*

将动态参数附加到 Add-Content 和 Set-Content 指令小子上

Add-ContentSet-Content cmdlets 可能需要额外的动态参数,这些参数会在运行时中添加。 为了提供这些动态参数,Windows PowerShell 内容提供者必须实现 System.Management.Automation.Provider.IContentCmdletProvider.GetContentWriterDynamicParameters* 方法来处理这些参数。 该方法检索指定路径上的动态参数,返回一个具有类似 cmdlet 类或 System.Management.Automation.RuntimeDefinedParameterDictionary 对象的属性和字段的对象。 Windows PowerShell 运行时使用返回的对象将参数添加到 cmdlet 中。

该 Windows PowerShell 容器提供者未实现此方法。 不过,以下代码是该方法的默认实现。

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

清算内容

您的内容提供者实现了 System.Management.Automation.Provider.IContentCmdletProvider.ClearContent* 方法以支持该 Clear-Content 命令。 该方法移除指定路径上的内容,但保持该项目完整。

以下是该提供者系统 管理.自动化.提供者.IContentCmdletProvider.ClearContent* 方法的实现。

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

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

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

    OdbcDataAdapter da = GetAdapterForTable(tableName);

    if (da == null)
    {
        return;
    }

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

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

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

} // ClearContent

关于实现ClearContent需要记住的事项

以下条件可能适用于 System.Management.Automation.Provider.IContentCmdletProvider.ClearContent*的实现:

将动态参数附加到 Clear-Content cmdlet

Clear-Content该 cmdlet 可能需要运行时添加的额外动态参数。 为了提供这些动态参数,Windows PowerShell 内容提供者必须实现 System.Management.Automation.Provider.IContentCmdletProvider.ClearContentDynamicParameters* 方法来处理这些参数。 该方法检索到指定路径上的参数。 该方法检索指定路径上的动态参数,返回一个具有类似 cmdlet 类或 System.Management.Automation.RuntimeDefinedParameterDictionary 对象的属性和字段的对象。 Windows PowerShell 运行时使用返回的对象将参数添加到 cmdlet。

该 Windows PowerShell 容器提供者未实现此方法。 不过,以下代码是该方法的默认实现。

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

代码示例

完整的示例代码请参见 AccessDbProviderSample06 代码示例

定义对象类型与格式化

在编写提供者时,可能需要向现有对象添加成员或定义新对象。 完成后,你必须创建一个 Types 文件,Windows PowerShell 可以用来识别对象的成员,以及一个定义对象显示方式的 Format 文件。 更多信息请参见 “扩展对象类型与格式”

构建 Windows PowerShell 提供者

请参阅 如何注册指令、提供者和托管应用程序

测试Windows PowerShell提供者

当你的 Windows PowerShell 提供者已注册 Windows PowerShell 后,可以通过命令行运行支持的 cmdlet 来测试。 例如,测试示例内容提供者。

使用 Get-Content 该 cmdlet 在参数指定的 Path 路径上检索数据库表中指定项目的内容。 该 ReadCount 参数指定定义内容读取器可读取的项目数量(默认为1)。 通过以下命令条目,命令小工具从表中检索两行(项)并显示其内容。 请注意,以下示例输出使用了一个虚构的Access数据库。

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

另请参阅

创建 Windows PowerShell 提供者

设计您的Windows PowerShell提供者

扩展对象类型与格式化

Implement a Navigation Windows PowerShell provider

如何注册 Cmdlet、提供者和托管应用程序

Windows PowerShell SDK

Windows PowerShell 程序员指南