如何创建PowerShell“主”功能?

时间:2014-01-08 16:18:05

标签: powershell

我过去使用过很多脚本语言,现在正在学习PowerShell。在这些其他语言中,我通常在定义函数之前定义我的主要逻辑,因此阅读代码的人将首先关注主逻辑。这通常采用在文件顶部创建“主”函数或类的形式,并在底部调用它。

在PowerShell中,此模式可能如下所示:

$main = {
    ...
    do-something
    ...
}
function do-something() {
    <function definition here>
}

& $main

这很好用,但我现在想利用PowerShell远程运行代码的能力。由于$main是PowerShell ScriptBlock对象,我[想我]可以在这样的远程机器上运行该代码:

Invoke-Command -ScriptBlock $main -ComputerName whatever;

但是,远程机器对我的功能一无所知,因为它的定义超出了$main的范围。当然,我可以将函数的定义移到$ main中,但是我必须把它放在主逻辑之上,我才回到第一个问题。

是否有一种编写PowerShell脚本的通用模式,其中主逻辑位于文件顶部的函数或脚本块中,类似于许多传统语言?人们如何编写复杂的脚本 - 他们是否总是从上到下编写它们,根据需要在顶部插入函数?

有人将此标记为问题Why do I need to have my functions written first in my Powershell script?的可能副本。这不是重复的。我知道为什么需要首先定义函数 - 我一直在编程,因为C是很酷的新语言。

我要求的是在poweshell中这样做的模式,特别是在希望能够远程运行主代码的情况下。

是否存在将main块放在powershell脚本顶部的常见模式? Powershell语法和习语与更传统的语言,甚至是许多流行的现代脚本语言都有很大的不同,将主要逻辑移到顶端的模式并不明显(或者可能是这样,但我还没有找到正确的例子)或文件)

4 个答案:

答案 0 :(得分:8)

如果要在PowerShell中模拟“传统”结构,一种方法是使用Begin, Process and End keywords

这些可以在脚本中以任何顺序出现,但Begin块将始终首先运行,因此您可以将其放在脚本的底部,并将函数定义放在那里,并拥有主要逻辑位于ProcessEnd块中的脚本顶部。

答案 1 :(得分:7)

从顶部到底部读取脚本,因此在启动之前不能使用任何引用。但是,您可以创建一个脚本/脚本块来模拟编程语言的工作方式。

在像c#这样的编程语言中,main部分是一个函数。当应用程序完成加载必要的部分(如核心功能等),并且事件处理程序调用主函数以启动聚会。使用下面的方法,您将模拟相同的行为。

带脚本块的Invoke-Command

要将其与invoke-command一起使用,只需将上面的示例包装在新的脚本块中并使用它。

$script = {
    #Main-function
    function main {
        #starting helper function
        helper-func
    }

    #Helpers
    function helper-func {
        Write-host "foo"
    }

    #Entry point
    main
}

Invoke-Command -ComputerName mycomputer -Scriptblock $script

带文件的Invoke-Command

或者将其保存到文件中,并以此方式调用。

Script.ps1

#Main-function
function main {
    #starting helper function
    helper-func
}

#Helpers
function helper-func {
    Write-host "foo"
}

#Entry point
main

用法:

Invoke-Command -ComputerName mycomputer -FilePath '.\Script.ps1'

答案 2 :(得分:1)

AFAIK,无法在脚本文件(.ps1)中的主代码之后定义子例程,因为代码是按顺序处理的,如果在定义之前调用函数,则会出现错误

子功能

如果你想组织它以便更清楚哪些部分是主代码以及哪些部分是子程序,你可以在函数内声明函数(也可以将它们作为函数范围),但是他们需要在开始时进行,以便在主代码调用它们之前对它们进行定义:

function main {
  param(
    # [...]
  )

  function sub1 {
    # [...]
  }
  function sub2 {
    # [...]
  }

  # [main code, using sub1 and sub2]
}

模块

我知道在主函数之后出现子程序的唯一方法是使用PowerShell module。导入模块时,在执行任何代码之前,将导入其定义的所有函数。

因此,当您执行main函数时,所有子例程都已定义。

模块背景

如果你不熟悉模块,那很简单。只需将所有代码放入.psm1文件中(它不必仅包含函数定义,但在导入模块时执行任何其他代码)。然后使用

将其导入会话
Import-Module <path to .psm1 file>

(请注意,您需要在同一会话中将-Force开关添加到重新导入。)

远程运行模块

如果你想远程运行它,这应该有效:

Invoke-Command -ScriptBlock {Import-Module \\<unc path to .psm1 file>; main} -ComputerNam whatever

答案 3 :(得分:1)

我个人更喜欢使用ScriptBlockInvoke-Command传递到远程计算机。话虽这么说,编写一个完整的“开始 - 完成”脚本文件,执行一些本地任务,然后使用-File参数将其部署到本地(和/或远程)计算机是完全有效的。 Invoke-Command

此代码示例将动态生成PowerShell脚本文件,然后将其部署到目标系统。这样,您就不必手动管理多个脚本文件:

  1. 执行某项任务的脚本
  2. 包装脚本#1
  3. 的“部署”脚本

    ...

    # 1. Declare script path and contents, and array of target systems
    $ScriptPath = '{0}\test\script.ps1' -f $env:SystemDrive;
    $ScriptContents = 'Get-Process';
    $TargetSystems = 'server01', 'server02', 'server03';
    
    # 2. Create / generate script file, and deploy the script to specified computers
    mkdir -Path (Split-Path -Path $ScriptPath -Parent) -ErrorAction SilentlyContinue;
    Set-Content -Path $ScriptPath -Value $ScriptContents;
    Invoke-Command -ComputerName $TargetSystems -File $ScriptPath;
    

    如果您像我一样,并且更喜欢使用ScriptBlock,则在部署之前,您的脚本的整个内容必须包含在ScriptBlock中与Invoke-Command

    以下是一个例子:

    # 1. Build a ScriptBlock from a PowerShell "Here-String" (multi-line string)
    $ScriptBlock = [ScriptBlock]::Create(@'
        function Helper1 {
            [CmdletBinding()]
            param (
            )
    
            begin {
                Write-Host -Object ('{0} was called' -f $PSCmdlet.MyInvocation.InvocationName);
            }
            process { }
            end { }
        }
    
        function Helper2 {
            [CmdletBinding()]
            param (
            )
    
            begin {
                Write-Host -Object ('{0} was called' -f $PSCmdlet.MyInvocation.InvocationName);
            }
            process { }
            end { }
        }
    
        function Main {
            [CmdletBinding()]
            param (
            )
    
            begin {
                Helper1;
                Helper2;
            }
            process { }
            end { }
        }
    
        # Call Main
        Main;
    '@);
    
    # 2. Call the ScriptBlock on localhost
    Invoke-Command -ComputerName localhost -ScriptBlock $ScriptBlock;
    

    输出

    上述命令的输出如下所示:

    Helper1 was called
    Helper2 was called