PowerShell,按需Internet的自动负载功能

发布于 2025-01-25 06:04:03 字数 1043 浏览 4 评论 0 原文

它向我指出(在 powerShell,复制bash Parallel ping ),我可以从Internet上加载一个函数,如下所示

iex (irm https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1)

: URL引用 test-connectionAsync.ps1 包含两个函数: ping-subnet test-connectionAseNAsync

这让我想知道我是否可以定义旁路功能在我的个人模块中,它是虚拟函数,一旦被调用,它们就会永久覆盖。例如,

function Ping-Subnet <mimic the switches of the function to be loaded> {
    if <function is not already loaded from internet> {
        iex (irm https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1)
    }
    # Now, somehow, permanently overwrite Ping-Subnet to be the function that loaded from the URL
    Ping-Subnet <pass the switches that we mimicked to the required function that we have just loaded>
}

这将非常简单地允许我直接从我的模块引用许多有用的脚本,但是无需在加载模块时必须从Internet上加载它们(即仅按需加载功能,我调用它们,除非我需要它们,否则我通常永远不会调用这些功能)。

It was pointed out to me (in PowerShell, replicate bash parallel ping) that I can load a function from the internet as follows:

iex (irm https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1)

The url referenced Test-ConnectionAsync.ps1 contains two functions: Ping-Subnet and Test-ConnectionAsync

This made me wonder if I could then define bypass functions in my personal module that are dummy functions that will be permanently overridden as soon as they are invoked. e.g.

function Ping-Subnet <mimic the switches of the function to be loaded> {
    if <function is not already loaded from internet> {
        iex (irm https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1)
    }
    # Now, somehow, permanently overwrite Ping-Subnet to be the function that loaded from the URL
    Ping-Subnet <pass the switches that we mimicked to the required function that we have just loaded>
}

This would very simply allow me to reference a number of useful scripts directly from my module but without having to load them all from the internet upon loading the Module (i.e. the functions are only loaded on demand, when I invoke them, and I will often never invoke the functions unless I need them).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

小ぇ时光︴ 2025-02-01 06:04:03

您可以使用解析器在远程脚本中找到功能并将其加载到您的范围中。这不会是自我升级功能,但应该比您想完成的工作更安全。

using namespace System.Management.Automation.Language

function Load-Function {
    [cmdletbinding()]
    param(
        [parameter(Mandatory, ValueFromPipeline)]
        [uri] $URI
    )

    process {
        try {
            $funcs = Invoke-RestMethod $URI
            $ast = [Parser]::ParseInput($funcs, [ref] $null, [ref] $null)
            foreach($func in $ast.FindAll({ $args[0] -is [FunctionDefinitionAst] }, $true)) {
                if($func.Name -in (Get-Command -CommandType Function).Name) {
                    Write-Warning "$($func.Name) is already loaded! Skipping"
                    continue
                }
                New-Item -Name "script:$($func.Name)" -Path function: -Value $func.Body.GetScriptBlock()
            }
        }
        catch {
            Write-Warning $_.Exception.Message
        }
    }
}

Load-Function https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1
Ping-Subnet # => now is available in your current session.

You could use the Parser to find the functions in the remote script and load them into your scope. This will not be a self-updating function, but should be safer than what you're trying to accomplish.

using namespace System.Management.Automation.Language

function Load-Function {
    [cmdletbinding()]
    param(
        [parameter(Mandatory, ValueFromPipeline)]
        [uri] $URI
    )

    process {
        try {
            $funcs = Invoke-RestMethod $URI
            $ast = [Parser]::ParseInput($funcs, [ref] $null, [ref] $null)
            foreach($func in $ast.FindAll({ $args[0] -is [FunctionDefinitionAst] }, $true)) {
                if($func.Name -in (Get-Command -CommandType Function).Name) {
                    Write-Warning "$($func.Name) is already loaded! Skipping"
                    continue
                }
                New-Item -Name "script:$($func.Name)" -Path function: -Value $func.Body.GetScriptBlock()
            }
        }
        catch {
            Write-Warning $_.Exception.Message
        }
    }
}

Load-Function https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1
Ping-Subnet # => now is available in your current session.
Smile简单爱 2025-02-01 06:04:03
function Ping-Subnet{
    $toImport = (IRM "https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1").
                Replace([Text.Encoding]::UTF8.GetString((239,187,191)),"")
    NMO([ScriptBlock]::Create($toImport))|Out-Null
    $MyInvocation.Line|IEX
}
function Test-ConnectionAsync{
    $toImport = (IRM "https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1").
                Replace([Text.Encoding]::UTF8.GetString((239,187,191)),"")
    NMO([ScriptBlock]::Create($toImport))|Out-Null
    $MyInvocation.Line|IEX
}

Ping-Subnet -Result Success

Test-ConnectionAsync -Computername $env:COMPUTERNAME

结果:

Computername   Result
------------   ------
192.168.1.1   Success
192.168.1.2   Success
192.168.1.146 Success

Computername IPAddress                  Result
------------ ---------                  ------
HOME-PC      fe80::123:1234:ABCD:EF12  Success
function Ping-Subnet{
    $toImport = (IRM "https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1").
                Replace([Text.Encoding]::UTF8.GetString((239,187,191)),"")
    NMO([ScriptBlock]::Create($toImport))|Out-Null
    $MyInvocation.Line|IEX
}
function Test-ConnectionAsync{
    $toImport = (IRM "https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1").
                Replace([Text.Encoding]::UTF8.GetString((239,187,191)),"")
    NMO([ScriptBlock]::Create($toImport))|Out-Null
    $MyInvocation.Line|IEX
}

Ping-Subnet -Result Success

Test-ConnectionAsync -Computername $env:COMPUTERNAME

Result:

Computername   Result
------------   ------
192.168.1.1   Success
192.168.1.2   Success
192.168.1.146 Success

Computername IPAddress                  Result
------------ ---------                  ------
HOME-PC      fe80::123:1234:ABCD:EF12  Success
仅一夜美梦 2025-02-01 06:04:03

是的,它应该起作用。调用 test-connectionAsync.ps1 在一个函数中,将在包装函数的范围中创建定义的函数。您将能够调用任何包装功能,直到功能的范围结束为止。

如果您将包装器命名和包装功能不同,则可以检查是否已使用类似...

”

否则,您需要获得更多的创意。

也就是说, 继续谨慎 。像这样,远程代码执行充满了安全问题,尤其是在我们谈论的方式中,没有验证 test-connectionAsenc.ps1

Yes, it should work. Calling Test-ConnectionAsync.ps1 from with-in a function will create the functions defined with-in, in the wrapping function's scope. You will be able to call any wrapped functions until the function's scope ends.

enter image description here

If you name the wrapper and wrapped functions differently, you can check whether the function has been declared with something like...

enter image description here

Otherwise, you need to get more creative.

This said, PROCEED WITH CAUTION. Remote code execution, like this, is fraught with security issues, especially in the way we're talking about it i.e., no validation of Test-ConnectionAsync.ps1.

香橙ぽ 2025-02-01 06:04:03

fors1k的答案值得获得信用,因为提出了该方法的巧妙基础< /strong>:

虽然Fors1k的解决方案通常会起作用,但这里是一种简化的,可靠的替代方案,可以防止潜在的代码重新执行:

function Ping-Subnet{
  $uri = 'https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1'
  # Define and session-globally import a dynamic module based on the remote
  # script's content.
  # Any functions defined in the script would automatically be exported.
  # However, unlike with persisted modules, *aliases* are *not* exported by 
  # default, which the appended Export-ModuleMember call below compensates for.
  # If desired, also add -Variable * in order to export variables too.
  # Conversely, if you only care about functions, remove the Export-ModuleMember call.
  $dynMod = New-Module ([scriptblock]::Create(
    ((Invoke-RestMethod $uri)) + "`nExport-ModuleMember -Function * -Alias *")
  )
  # If this stub function shadows the newly defined function in the dynamic
  # module, remove it first, so that re-invocation by name uses the new function.
  # Note: This happens if this stub function is run in a child scope, such as
  #       in a (non-dot-sourced) script rather than in the global scope.
  #       If run in the global scope, curiously, the stub function seemingly
  #       disappears from view right away - not even Get-Command -All shows it later.
  $myName = $MyInvocation.MyCommand.Name
  if ((Get-Command -Type Function $myName).ModuleName -ne $dynMod.Name) {
    Remove-Item -LiteralPath "function:$myName"
  }
  # Now invoke the newly defined function of the same name, passing the arguments
  # through.
  & $myName @args
}

具体来说,此实现确保了:

  • em> Aliases 在远程脚本中定义的

    是导出的。此外(只需删除+“`nexport -modulemember -function * -Alias *'从上面的代码中删除。

  • 重新转换鲁棒性针对 new ,模块定义的函数实现 - 即使存根函数在子范围内运行,例如在(非点源)中脚本。

    • 在子范围内运行时, $ myInvocation.line | iex iex invoke-expression cmdlet)会导致无限循环,因为当时的存根功能本身仍然有效。


  • 所有收到的参数均在不重新评估的情况下通过重新发电。

    • 使用内置的魔术来剥落自动 $ args variable(@args )仅通过接收到的已经扩展的参数,支持两者都命名和位置参数。 [2]

    • $ myInvocation.line | iex 有两个潜在的问题:

      • 如果调用命令行包含多个命令,则它们是 ash em 重复。

        • 您可以通过替换(get-pscallstack)[1] .position.text $ myInvocation.line 来解决此特定问题下一个问题。
      • 两种 $ myInvocation.line (get-pscallstack)[1] .position.text 包含在 em> doducted 中传递的参数/em>(未评估)表格,这会导致其通过 Invoke-Expression 进行重新评估,并且其危险至少在假设上,此重新评估可能涉及冗长的命令,其输出用作其输出为参数或更糟糕的是,具有副作用的命令无法重复或不应该重复。







将技术范围划分到给定的本地脚本

如果您尝试在脚本中使用该技术,则下载的功能可用 session-globally 可能是不必要的。 不应该影响会议的全球状态;也就是说,您可能希望在脚本退出时通过动态模块导出的功能消失。

这需要有两个额外的步骤:

  • 将动态模块管道到 import-module ,这是能够在使用Remove-Module

  • 呼叫 remove-module 带有动态模块,然后退出以卸载。

function Ping-Subnet{
  $uri = 'https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1'
  # Save the module in a script-level variable, and pipe it to Import-Module
  # so that it can be removed before the script exits.
  $script:dynMod = New-Module ([scriptblock]::Create(
    ((Invoke-RestMethod $uri)) + "`nExport-ModuleMember -Function * -Alias *")
  ) | Import-Module -PassThru
  # If this stub function shadows the newly defined function in the dynamic
  # module, remove it first, so that re-invocation by name use the new function.
  # Note: This happens if this stub function is run in a child scope, such as
  #       in a (non-dot-sourced) script rather than in the global scope.
  #       If run in the global scope, curiously, the stub function seemingly
  #       disappears from view right away - not even Get-Command -All shows it later.
  $myName = $MyInvocation.MyCommand.Name
  if ((Get-Command -Type Function $myName).ModuleName -ne $dynMod.Name) {
    Remove-Item -LiteralPath "function:$myName"
  }
  # Now invoke the newly defined function of the same name, passing the arguments
  # through.
  & $myName @args
}

# Sample commands to perform in the script.
Ping-Subnet -?
Get-Command Ping-Subnet, Test-ConnectionAsync | Format-Table

# Before exiting, remove (unload) the dynamic module.
$dynMod | Remove-Module

[1]这假定 new-Module 调用本身是在模块之外进行的;如果在模块中制成,至少该模块的命令会看到自动出口的函数;如果该模块使用隐式导出行为(这是罕见且不建议),则该模块的自动出口功能将包含在该模块的导出中,因此再次成为session-cession-cession-cession-clobally。

[2]这个魔术有一个限制,但是,它只会表面很少: [switch] parameters 直接连接的布尔值参数不是支持的(例如, -casessentive:$ true ) - 参见此答案

Fors1k's answer deserves the credit for coming up with the clever fundamentals of the approach:

  • Download and execute the remote script's content in a dynamic module created with
    New-Module (whose built-in alias is nmo), which causes the script's functions to be auto-exported and to become available session-globally[1]

    • Note that dynamic modules aren't easy to discover, because they're not shown in
      Get-Module's output; however, you can discover them indirectly, via the .Source property of the command-info objects output by Get-Command:

      Get-Command | Where Source -like __DynamicModule_*
      
    • That the downloaded functions become available session-globally may be undesired if you're trying to use the technique inside a script that shouldn't affect the session's global state - see the bottom section for a solution.

  • Then re-invoke the function, under the assumption that the original stub function has been replaced with the downloaded version of the same name, passing the received arguments through.

While Fors1k's solution will typically work, here is a streamlined, robust alternative that prevents potential, inadvertent re-execution of code:

function Ping-Subnet{
  $uri = 'https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1'
  # Define and session-globally import a dynamic module based on the remote
  # script's content.
  # Any functions defined in the script would automatically be exported.
  # However, unlike with persisted modules, *aliases* are *not* exported by 
  # default, which the appended Export-ModuleMember call below compensates for.
  # If desired, also add -Variable * in order to export variables too.
  # Conversely, if you only care about functions, remove the Export-ModuleMember call.
  $dynMod = New-Module ([scriptblock]::Create(
    ((Invoke-RestMethod $uri)) + "`nExport-ModuleMember -Function * -Alias *")
  )
  # If this stub function shadows the newly defined function in the dynamic
  # module, remove it first, so that re-invocation by name uses the new function.
  # Note: This happens if this stub function is run in a child scope, such as
  #       in a (non-dot-sourced) script rather than in the global scope.
  #       If run in the global scope, curiously, the stub function seemingly
  #       disappears from view right away - not even Get-Command -All shows it later.
  $myName = $MyInvocation.MyCommand.Name
  if ((Get-Command -Type Function $myName).ModuleName -ne $dynMod.Name) {
    Remove-Item -LiteralPath "function:$myName"
  }
  # Now invoke the newly defined function of the same name, passing the arguments
  # through.
  & $myName @args
}

Specifically, this implementation ensures:

  • That aliases defined in the remote script are exported as well (just remove + "`nExport-ModuleMember -Function * -Alias *" from the code above if that is undesired.

  • That the re-invocation robustly targets the new, module-defined implementation of the function - even if the stub function runs in a child scope, such as in a (non-dot-sourced) script.

    • When run in a child scope, $MyInvocation.Line|IEX (iex is a built-in alias of the Invoke-Expression cmdlet) would result in an infinite loop, because the stub function itself is still in effect at that time.
  • That all received arguments are passed through on re-invocation without re-evaluation.

    • Using the built-in magic of splatting the automatic $args variable (@args) passes only the received, already expanded arguments through, supporting both named and positional arguments.[2]

    • $MyInvocation.Line|IEX has two potential problems:

      • If the invoking command line contained multiple commands, they are all repeated.

        • You can solve this particular problem by substituting (Get-PSCallStack)[1].Position.Text for $MyInvocation.Line, but that still wouldn't address the next problem.
      • Both $MyInvocation.Line and (Get-PSCallStack)[1].Position.Text contain the arguments that were passed in unexpanded (unevaluated) form, which causes their re-evaluation by Invoke-Expression, and the perils of that are that, at least hypothetically, this re-evaluation could involve lengthy commands whose output served as arguments or, worse, commands that had side effects that cannot or should not be repeated.


Scoping the technique to a given local script:

That the downloaded functions become available session-globally may be undesired if you're trying to use the technique inside a script that shouldn't affect the session's global state; that is, you may want the functions exported via the dynamic module to disappear when the script exits.

This requires two extra steps:

  • Piping the dynamic module to Import-Module, which is the prerequisite for being able to unload it before exiting with Remove-Module

  • Calling Remove-Module with the dynamic module before exiting in order to unload it.

function Ping-Subnet{
  $uri = 'https://raw.githubusercontent.com/proxb/AsyncFunctions/master/Test-ConnectionAsync.ps1'
  # Save the module in a script-level variable, and pipe it to Import-Module
  # so that it can be removed before the script exits.
  $script:dynMod = New-Module ([scriptblock]::Create(
    ((Invoke-RestMethod $uri)) + "`nExport-ModuleMember -Function * -Alias *")
  ) | Import-Module -PassThru
  # If this stub function shadows the newly defined function in the dynamic
  # module, remove it first, so that re-invocation by name use the new function.
  # Note: This happens if this stub function is run in a child scope, such as
  #       in a (non-dot-sourced) script rather than in the global scope.
  #       If run in the global scope, curiously, the stub function seemingly
  #       disappears from view right away - not even Get-Command -All shows it later.
  $myName = $MyInvocation.MyCommand.Name
  if ((Get-Command -Type Function $myName).ModuleName -ne $dynMod.Name) {
    Remove-Item -LiteralPath "function:$myName"
  }
  # Now invoke the newly defined function of the same name, passing the arguments
  # through.
  & $myName @args
}

# Sample commands to perform in the script.
Ping-Subnet -?
Get-Command Ping-Subnet, Test-ConnectionAsync | Format-Table

# Before exiting, remove (unload) the dynamic module.
$dynMod | Remove-Module

[1] This assumes that the New-Module call itself is made outside of a module; if it is made inside a module, at least that module's commands see the auto-exported functions; if that module uses implicit exporting behavior (which is rare and not advisable), the auto-exported functions from the dynamic module would be included in that module's exports and therefore again become available session-globally.

[2] This magic has one limitation, which, however, will only rarely surface: [switch] parameters with a directly attached Boolean argument aren't supported (e.g., -CaseSensitive:$true) - see this answer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文