![powershell远程](https://i-blog.csdnimg.cn/blog_migrate/90a94655116d6f076a85b46007472178.png)
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写入文件。 他们正在将信息走私到系统之外。 这是PowerShell的Heisenberg不确定原理。 如果您观察到某些内容,则进行更改。 将结果写入文件是一个活板门,不会影响管道的输出。 我可能是错的。
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版本的愿望清单
-
All this stuff I did, built in already with security and wonderfulness.我所做的所有这些工作,已经内置了安全性和出色性。
-
PowerShellRemoting, with security and wonderfulness. PowerShellRemoting中的所有内容,具有安全性和出色性。
-
Some kind of editor or schema installed in VS.NET for editing My.Types.ps1xml.VS.NET中安装的某种编辑器或架构,用于编辑My.Types.ps1xml。
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远程