powershell远程_PowerShell中的ScriptBlock和运行空间远程处理

powershell远程

powershell远程

When I first saw grokked PowerShell I wanted to be able to issue commands remotely. There's some great stuff going on with PowerShellRemoting over at GotDotNet, but that's remoting of the User Interface.

当我第一次 看到 古怪的PowerShell时,我希望能够远程发出命令。 在GotDotNet上执行PowerShellRemoting有很多很棒的事情,但这就是用户界面的远程处理。

I want to be able to issue commands to many different machines in a distributed fashion.

我希望能够以分布式方式向许多不同的计算机发出命令。

After some pair programming with Brian Windheim, we set up a Windows Service that would get a string of commands and return a string that was the output o those commands. I could then issue remote commands, but the result at the client was just strings. I was in PowerShell but I'd just made the equivalent of PSEXEC for PowerShell...so basically I'd gotten nowhere.

与Brian Windheim进行了一些配对编程后,我们设置了Windows服务,该服务将获取命令字符串,并返回作为这些命令输出的字符串。 然后,我可以发出远程命令,但客户端的结果只是字符串。 我当时在PowerShell中,但是我只是将PSEXEC等同于PowerShell ...所以基本上我一无所获。

Ideally I'd like to have behavior like this (but I don't):

理想情况下,我希望具有以下行为(但我没有):

using (Runspace myRunSpace = RunspaceFactory.CreateRunspace("COMPUTERNAME"))

使用( Runspace myRunSpace = RunspaceFactory .CreateRunspace(“ COMPUTERNAME”))

{

{

    myRunSpace.Open();}

myRunSpace.Open();}

But a Runspace is local and inproc. I don't see a really obvious and straightforward way to do this, considering that there's LOTS of internal and private stuff going on within PowerShell.

但是Runspace是本地的,并且是inproc的。 考虑到在PowerShell中存在很多内部和私有内容,我看不到一种真正明显而直接的方法。

I liked that the string in, string out remoting stuff worked fine, but really I want to get Objects back from the remote machine. So, I started using Reflection to poke around inside System.Management.Automation.Serializer, but that got evil quickly. Truly evil.

我喜欢输入字符串,输出字符串远程处理的东西工作正常,但实际上我想从远程计算机上获取对象。 因此,我开始使用Reflection来在System.Management.Automation.Serializer内部四处摸索,但这很快就变得很邪恶。 真邪恶。

Then I had an epiphany and remember the Export-CliXml cmdlet. It is the public cmdlet that uses the serializer I was trying to get to. It isn't the XmlSerializer. It's a serialized graph of objects with a rich enough description of those objects that the client doesn't necessarily need the CLR types. If reflection had a serialization format, it might look like this format.

然后我顿悟了一下,还记得Export-CliXml cmdlet。 使用我尝试访问的序列化程序的是公共cmdlet。 不是XmlSerializer。 它是对象的序列化图,具有对这些对象的足够丰富的描述,客户端不一定需要CLR类型。 如果反射具有序列化格式,则它可能看起来像这种格式。

Now, if I take the commands I was issuing to the remote "invoker" and export the result of the pipeline to this function XML format, I've just discovered my remoting server's wire format.

现在,如果我使用向远程“调用者”发出的命令,并将管道的结果导出为该功能XML格式,则我刚刚发现了远程服务器的有线格式。

This RunspaceInvoker type is hosted in a Windows Service, but it could be in any Remoting hosting process. I'll likely move it inside IIS for security reasons. The app.config for my service looks like this:

此RunspaceInvoker类型托管在Windows服务中,但可以在任何Remoting托管进程中。 出于安全原因,我可能会将其移入IIS。 我的服务的app.config看起来像这样:

<?xml version="1.0"  encoding="utf-8" ?>

<? XML文件 版本= “ 1.0 ”  encoding = “ utf-8 ”吗?>

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

<配置 xmlns = “ http://schemas.microsoft.com/.NetConfiguration/v2.0 ” >

  <system.runtime.remoting>

< system.runtime.remoting >

    <customErrors mode="Off"/>

< customErrors 模式= “关” />

    <application>

<申请>

      <channels>

<频道>

        <channel ref="http" port="8081"/>

<频道 ref = “ http ” port = “ 8081 ” />

      </channels>

</频道>

      <service>

<服务>

        <wellknown mode="SingleCall"

<知名 模式= “ SingleCall ”

                   type="Hanselman.RemoteRunspace.RunspaceInvoker,
                   Hanselman.RemoteRunspace" objectUri="remoterunspace.rem"/>

                   type = “ Hanselman.RemoteRunspace.RunspaceInvoker,
Hanselman.RemoteRunspace objectUri = “ remoterunspace.rem ” />

      </service>

</服务>

    </application>

</ application >

  </system.runtime.remoting>

</ system.runtime.remoting >

</configuration>

</配置>

Note the objectUri and port. We'll need those in a second. There's an installer class that I run using installutil.exe. I set the identity of the Windows Service and it starts up with net start RemoteRunspaceService.

注意objectUri和端口。 我们很快就会需要它们。 我使用installutil.exe运行一个安装程序类。 我设置了Windows服务的身份,并通过net start RemoteRunspaceService启动

This is the RunspaceInvoker (not the best name):

这是RunspaceInvoker(不是最佳名称):

 public class RunspaceInvoker : MarshalByRefObject

公共类RunspaceInvoker : MarshalByRefObject

 {

{

    public RunspaceInvoker(){}

公共RunspaceInvoker(){}

    public string InvokeScriptBlock(string scriptString)

公共字符串InvokeScriptBlock(字符串scriptString)

    {

{

        using (Runspace myRunSpace = RunspaceFactory.CreateRunspace())

使用( Runspace myRunSpace = RunspaceFactory .CreateRunspace())

        {

{

            myRunSpace.Open();

myRunSpace.Open();

            string tempFileName = System.IO.Path.GetTempFileName();

字符串tempFileName = System.IO。 路径.GetTempFileName();

            string newCommand = scriptString +                 " | export-clixml " + "\"" + tempFileName + "\"";

字符串newCommand = scriptString + “ | export-clixml” + “ \”“ + tempFileName + ” \“” ;

            Pipeline cmd = myRunSpace.CreatePipeline(newCommand);

管道cmd = myRunSpace.CreatePipeline(newCommand);

            Collection<PSObject> objectRetVal = cmd.Invoke();

集合< PSObject > objectRetVal = cmd.Invoke();

            myRunSpace.Close();

myRunSpace.Close();

            string retVal = System.IO.File.ReadAllText(tempFileName);

字符串retVal = System.IO。 File .ReadAllText(tempFileName);

            System.IO.File.Delete(tempFileName);

System.IO。 File .Delete(tempFileName);

            return retVal;

返回retVal;

        }

}

    }

}

 }

}

A command for the remote service comes into the scriptString parameter. For example we might pass in dir c:\temp as the string, or a whole long pipeline. We create a Runspace, open it and append "| export-clixml" and put the results in a tempFileName.

用于远程服务的命令进入scriptString参数。 例如,我们可以将dir c:\ temp作为字符串或整个长管道传递。 我们创建一个运行空间,将其打开并附加“ | export-clixml ”,然后将结果放入tempFileName中。

THOUGHT: It's a bummer I can't put the results in a variable or get it out of the Pipeline, but I think I understand why they force me to write the CLI-XML to a file. They are smuggling the information out of the system. It's the Heisenberg Uncertainly Principle of PowerShell. If you observe something, you change it. Writing the results to a file is a trapdoor that doesn't affect the output of the pipeline. I could be wrong though.

想法:这真是令人讨厌,我无法将结果放入变量或从管道中删除,但是我认为我理解为什么它们会迫使我将CLI-XML写入文件。 他们正在将信息走私到系统之外。 这是PowerShellHeisenberg不确定原理。 如果您观察到某些内容,则进行更改。 将结果写入文件是一个活板门,不会影响管道的输出。 我可能是错的。

Anyway, this doesn't need to be performant. I write it to a temp file, read the file in and delete it right away away. Then I return the serialized CLI-XML to the caller.The client portion is two parts. I probably should make a custom cmdlet, but I didn't really see a need. Perhaps someone can offer me a reason why.

无论如何,这不需要表现出色。 我将其写入临时文件,将其读入并立即删除。 然后我将序列化的CLI-XML返回给调用者。客户端部分分为两部分。 我可能应该制作一个自定义cmdlet,但是我并没有真正的需要。 也许有人可以给我一个理由。

For simplicity I first made this RunspaceProxy. Remember, this is the class that the client uses to invoke the command remotely.

为简单起见,我首先制作了这个RunspaceProxy。 请记住,这是客户端用于远程调用命令的类。

    public class RunspaceProxy

公共类RunspaceProxy

    {

{

        public RunspaceProxy()

公共RunspaceProxy()

        {

{

            HttpChannel chan = new HttpChannel();

HttpChannel chan =新的HttpChannel ();

            if (ChannelServices.GetChannel("http") != null)

如果( ChannelServices .GetChannel( “ http” )!= null )

            {

{

                ChannelServices.RegisterChannel(chan, false);

ChannelServices .RegisterChannel(chan, false );

            }

}

        }

}

        public Collection<PSObject> Execute(string command, string remoteurl)

公共集合< PSObject >执行(字符串命令,字符串remoteurl)

        {

{

            RunspaceInvoker proxy = (RunspaceInvoker)Activator.GetObject(                   typeof(RunspaceInvoker), remoteurl);

RunspaceInvoker代理=( RunspaceInvoker )激活器.GetObject( typeof ( RunspaceInvoker ),remoteurl);

            string stringRetVal = proxy.InvokeScriptBlock(command);

字符串stringRetVal = proxy.InvokeScriptBlock(命令);

            using (Runspace myRunSpace = RunspaceFactory.CreateRunspace())

使用( Runspace myRunSpace = RunspaceFactory .CreateRunspace())

            {

{

                myRunSpace.Open();

myRunSpace.Open();

                string tempFileName = System.IO.Path.GetTempFileName();

字符串tempFileName = System.IO。 路径.GetTempFileName();

                System.IO.File.WriteAllText(tempFileName, stringRetVal);

System.IO。 File .WriteAllText(tempFileName,stringRetVal);

                Pipeline cmd = myRunSpace.CreatePipeline(                    "import-clixml " + "\"" + tempFileName + "\"");

管道cmd = myRunSpace.CreatePipeline( “ import-clixml” + “ \”“ + tempFileName + ” \“” );

                Collection<PSObject> retVal = cmd.Invoke();

集合< PSObject > retVal = cmd.Invoke();

                System.IO.File.Delete(tempFileName);

System.IO。 File .Delete(tempFileName);

                return retVal;

返回retVal;

            }

}

        }

}

    }

}

I'm using the HTTP channel for debugging and ease of use with TcpTrace. The command to be executed comes in along with the remoteUrl. We make a RunspaceInvoker (the class we talked about a second ago) on the remote machine and it does the work via a call to InvokeScriptBlock. The CLI-XML comes back over the wire and now I have to make a tempfile on the client. Then, in order to 'deserialize' - a better word would be re-hydrate - the Collection of PSObjects, I make a local Runspace and call import-clixml and poof, a Collection<PSObject> is returned to the client. I delete the file immediately.

我正在使用HTTP通道进行调试和TcpTrace的易用性。 要执行的命令与remoteUrl一起提供。 我们在远程计算机上创建了一个RunspaceInvoker(我们之前讨论过的类),它通过调用InvokeScriptBlock来完成工作。 CLI-XML通过网络返回,现在我必须在客户端上创建一个临时文件。 然后,为了“反序列化”-一个更好的词是重新水化-PSObjects的集合,我创建了一个本地Runspace并调用import-clixml和poof,将Collection <PSObject>返回给客户端。 我立即删除文件。

Why is returning real PSObjects so important when I had strings working? Because now I can select, sort, and where my way around these PSObjects as if they were local - because they are. They are real and substantial. This will allow us to write scripts that blur the line between the local admin and remote admin.

当字符串正常工作时,为什么返回真正的PSObjects如此重要? 因为现在我可以选择,排序以及在这些PSObject周围的方式,好像它们是本地的一样-因为它们是本地的。 它们是真实而实质的。 这将使我们能够编写模糊本地管理员和远程管理员之间界限的脚本。

Now, all this has been C# so far, when does the PowerShell come in? Also, since I've worked so hard (well, not that hard) to get the return values integrated cleanly with PowerShell, what's a good way to get the remote calling of scripts integrated cleanly?

现在,到目前为止,所有这些都是C#,PowerShell何时发布? 另外,由于我已经非常努力(嗯,不是那么辛苦)以使返回值与PowerShell完全集成在一起,因此,有一种使脚本的远程调用完全集成的好方法是什么?

My first try I made a function RemoteInvoke() that took a command string. It worked, but felt tacky. Then I remembered how Jeffrey Snover said to look to Type Extensions when adding functionality rather than functions and cmdlets.

我的第一次尝试是使函数RemoteInvoke()接受了命令字符串。 它奏效,但发粘。 然后我想起了Jeffrey Snover在添加功能而不是功能和cmdlet如何看待Type Extensions

I made a My.Types.ps1xml file in my PSConfiguration directory and put this in it:

我在PSConfiguration目录中创建了My.Types.ps1xml文件,并将其放入其中:

<Types>

<类型>

  <Type>

<类型>

    <Name>System.Management.Automation.ScriptBlock</Name>

<名称> System.Management.Automation.ScriptBlock </名称>

    <Members>

<成员>

      <ScriptMethod>

< ScriptMethod >

        <Name>RemoteInvoke</Name>

<名称> RemoteInvoke </名称>

        <Script>

<脚本>

          if ($GLOBAL:remoteUrl -eq $null) { throw 'Set $GLOBAL:remoteUrl first!' }

if($ GLOBAL:remoteUrl -eq $ null){抛出“首先设置$ GLOBAL:remoteUrl!” }

          [System.reflection.assembly]::LoadWithPartialName("System.Runtime.Remoting") |                out-null

[System.reflection.assembly] :: LoadWithPartialName(“ System.Runtime.Remoting”)| 零

          $someDll = "C:\foo\Hanselman.RemoteRunspace.dll"

$ someDll =“ C:\ foo \ Hanselman.RemoteRunspace.dll”

          $asm = [System.Reflection.Assembly]::LoadFrom($someDll) | out-null

$ asm = [System.Reflection.Assembly] :: LoadFrom($ someDll)| 零

          $runspace = new-object Hanselman.RemoteRunspace.RunspaceProxy

$ runspace =新对象Hanselman.RemoteRunspace.RunspaceProxy

          $runspace.Execute([string]$this, $GLOBAL:remoteUrl);

$ runspace.Execute([string] $ this,$ GLOBAL:remoteUrl);

        </Script>

</脚本>

      </ScriptMethod>

</ ScriptMethod >

    </Members>

</成员>

  </Type>

</类型>

</Types>

</类型>

Then called Update-TypeData My.Types.ps1xml (actually it's in my profile so it happens automatically.)  This file adds a new method to the ScriptBlock type. A ScriptBlock is literally a block of script. It's a very natural "atom" for us to use.

然后称为Update-TypeData My.Types.ps1xml (实际上它在我的配置文件中,因此它会自动发生。)此文件向ScriptBlock类型添加了一个新方法。 ScriptBlock实际上是一个脚本块。 对于我们来说,这是一个非常自然的“原子”。

NOTE: I'd like to have the RemoteUrl be a parameter to the RemoteInvoke ScriptMethod, but I can't fine really any documentation on this. I'll update it when I figure it out, but for now it uses a $GLOBAL variable and freaks out if it's not set.

注意:我想将RemoteUrl作为RemoteInvoke ScriptMethod的参数,但是我对此并不能很好地使用任何文档。 当我弄清楚它时,我将对其进行更新,但是现在它使用了$ GLOBAL变量,并且如果未设置该变量,则会出现异常。

The RemoteInvoke loads the .NET System.Runtime.Remoting assembly, then it loads our Proxy assembly. Then it calls Execute, casting the [ScriptBlock] to a [string] because the Runspace takes a string.

RemoteInvoke加载.NET System.Runtime.Remoting程序集,然后加载我们的Proxy程序集。 然后,它调用Execute,将[ScriptBlock]强制转换为[string],因为Runspace需要一个字符串。

For example, at a PowerShell prompt if I do this:

例如,如果执行此操作,则在PowerShell提示符下:

PS[79] C:\> $remoteUrl="http://remotecomputer:8081/RemoteRunspace.rem"
PS[80] C:\PS[80] C:\> 2+2
4
PS[81] C:\> (2+2).GetType()

PS [79] C:\> $ remoteUrl =“ http:// remotecomputer:8081 / RemoteRunspace.rem” PS [80] C:\ PS [80] C:\> 2 + 2 4 PS [81] C:\>(2 + 2).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType

IsPublic IsSerial名称BaseType -------- -------- ---- -------- True True Int32 System.ValueType

PS[82] C:\> {2+2}.GetType()

PS [82] C:\> {2 + 2} .GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    ScriptBlock                              System.Object

IsPublic IsSerial名称BaseType -------- -------- ---- -------- 真假ScriptBlock System.Object

PS[83] C:\> {2+2}
4
PS[84] C:\> {2+2}.RemoteInvoke()
4
PS[85] C:\>
{2+2}.RemoteInvoke().GetType()IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType

PS [83] C:\> {2 + 2} 4 PS [84] C:\> {2 + 2} .RemoteInvoke() 4 PS [85] C:\> {2 + 2} .RemoteInvoke()。GetType() IsPublic IsSerial名称BaseType -------- -------- ---- -------- True True Int32 System.ValueType

Note the result of the last line. The value that comes out of RemoteInvoke is an Int32, not a string. The result of that ScriptBlock executing is a PowerShell type that I can work with elsewhere in my local script.

注意最后一行的结果。 从RemoteInvoke出来的值是Int32,而不是字符串。 执行ScriptBlock的结果是可以在本地脚本的其他地方使用的PowerShell类型。

Here's the CLI-XML that went over the wire (just to make it clear it's not XmlSerializer XML):

这是遍及整个网络的CLI-XML(只是为了弄清楚它不是XmlSerializer XML):

<Objs Version="1.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">

< Objs 版本= “ 1.1 ” xmlns = “ http://schemas.microsoft.com/powershell/2004/04 ” >

  <I32>4</I32>

< I32 > 4 </ I32 >

</Objs>

</ Objs >

This 2+2 stuff is a terse and simple example, but this technique works with even large and complex object graphs like the FileInfos and FileSystemInfo objects that are returned from dir (get-childitem).

这个2 + 2的东西是一个简洁的示例,但是该技术甚至可以用于从dir(get-childitem)返回的FileInfos和FileSystemInfo对象之类的大型复杂对象图。

In this screenshot we do a get-process on the remote machine then sort and filter the results just as we would/could if the call were local.

在此屏幕截图中,我们在远程计算机上执行了一个get-process,然后对结果进行排序和过滤,就像在本地调用一样/可以。

My WishList for the Next Version of PowerShell

我的下一个PowerShell版本的愿望清单

Thanks again to Brian Windheim for the peer programming today that jump started this!

再次感谢Brian Windheim今天进行的对等编程,这开始了!

翻译自: https://www.hanselman.com/blog/scriptblock-and-runspace-remoting-in-powershell

powershell远程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值