황현동 블로그 개발, 인생, 유우머

160204 파워쉘 함수, cmdlet, 외부모듈사용

Tags:




  • 작년 가을부터 파워쉘에 관심이 생겨서 공부를 하기 시작했는데
  • 그다지 파워쉘을 활용할 일이 많이 없었다.

  • 하지만 올해부터는 Windows 10 IoT Core 에서 개발이 예정되어 있고,
  • 원격으로 붙을수 있는 쉘이 파워쉘뿐이다보니 아주 손발처럼 써야 할 상황이 되었다.

  • 이번 글에서는 파워쉘을 파워풀하게 사용하기 위해 커스톰 function, 커스톰 dll 을 제작하여 활용하는 방법을 소개한다.
  • 본론에 앞서 필자의 입장을 미리 밝히자면 필자는 파워쉘이 상당히 익숙해 지고 좋아졌지만 실용적으로는 간단한 스크립팅만 파워쉘로 구현하며 복잡한 기능은 커스톰 닷넷 dll제작해서 붙일것을 추천드린다.
  • 독자분들도 이글을 읽고 파워쉘 코딩의 즐거움을 함께 느낄수 있었으면 좋겠다.

  • 파워쉘이 처음이거나 기본 문법에 자신이 없으신 독자들은 필자의 파워쉘 기본문법 글을 먼저 선행하시길 권장드린다.
  • http://hhd2002.blogspot.kr/2015/11/151119.html




alias

alias 는 이름이 긴 function, cmdlet, path를 짧게 줄이는 역할을 한다. 주의할점은 파워쉘의 alias로는 bash처럼 파라미터까지 포함해서 만들수는 없다.

예제 : alias 정의

vim, sublime을 alias로 잡아서 사용 ```powershell PS C:\temp> Set-Alias vim “c:\hhdcommand\vim74\vim.exe” Set-Alias sublime “c:\hhdcommand\Sublime Text 2.0.2\sublime.exe”

PS C:\temp> sublime dir.txt

PS C:\temp> vim dir.txt


<br/>
<br/>
<br/>

## function
> function은 파워쉘의 스크립트를 확장할때 가장 기본이 되는 단위다.
> 가장 간단하게는 입출력이 없이 긴 스크립트 문을 축약하느라 사용하는 경우도 있고,
> 복잡하게 사용할때는 입력, 출력, 강타입, 파이프라인, 도움말주석처리 등을 모두 적용해서 사용할 수 있다.
> 여기서는 간단한 function부터 function의 풀스펙까지 모두 활용해 본다.
 
#### 예제 : function을 입출력 없는 축약파라미터 대체재로 사용
> ls -Force 와 같이 자주 사용하는 파라미터를 축약하는 용도로 입력파라미터 정의없이 사용할 수 있다.
```powershell
PS C:\temp> function lsforce
{
    Get-ChildItem -Force
}



PS C:\temp> lsforce
    디렉터리: C:\temp
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----     2016-02-04   오후 4:22            790 dir.txt


예제 : 복잡한 파이프라이닝 된 스크립트

ps 명령어는 너무 많은 정보를 리스팅해주기 때문에, ps2 를 만들어서 WorkingSet(사용중 메모리) 기준으로 상위10개만 노출되며 WS는 메가바이트단위로 표시하며, 전체적으로 리스트를 예쁘게 보이게 했다. ps2 도 역시 입력파라미터가 없는데 이 경우 ps2() 처럼 명시적으로 정의해도 아무 문제 없다. ```powershell PS C:\temp\abcd1234qwer> function ps2() { ps | sort WS -Descending | select Id, Name, @{l=”WS(M)”; e={int}}, Path -First 10 | ft -Au toSize -Wrap }

PS C:\temp\abcd1234qwer> ps2

Id Name WS(M) Path – —- —– —- 4 System 376 6624 devenv 284 C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe 15640 marxico 198 C:\Program Files (x86)\Marxico\marxico.exe 3348 MsMpEng 166 15680 powershell 155 C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe 18068 chrome 128 C:\Program Files (x86)\Google\Chrome\Application\chrome.exe 16776 chrome 95 C:\Program Files (x86)\Google\Chrome\Application\chrome.exe 7660 Microsoft.VsHub.Server.HttpHost 90 C:\Program Files (x86)\Common Files\Microsoft Shared\VsHub\1.0.0.0\Microsof t.VsHub.Server.HttpHost.exe 4460 explorer 86 C:\WINDOWS\Explorer.EXE 21784 chrome 79 C:\Program Files (x86)\Google\Chrome\Application\chrome.exe


<br/>

#### 예제 : 축약파라미터 버전에서 중요한 파라미터를 입력으로 받아줌.
> cd는 정확히 디렉토리를 입력했을때만 동작하기에 cd2를 만들어서 패턴으로 매칭되면 디렉토리 이동되도록 개선했다.
```powershell
PS C:\temp> 
function cd2($keyword)
{
    cd *$keyword*
}



PS C:\temp> mkdir abcd1234qwer

PS C:\temp> cd2 1234


예제 : function의 헤더주석을 붙여서 요약, 파라미터, 예제 표시

파워쉘을 이용하다보면 구글링보다는 help xxx -full 로 파라미터를 확인하고, example을 확인하는것이 더 정신건강에 좋다. 커스톰 function 들도 주석의 규칙을 이용해서 요약, 파라미터, 예제 를 help 에 뜨게 할 수 있다. ```powershell PS C:\temp\abcd1234qwer> <# .SYNOPSIS rm -Recurse -Force $path .PARAMETER path 파일, 디렉토리 경로 .EXAMPLE rmforce aaa #> function rmforce($path) { rm -Recurse -Force $path }

PS C:\temp> help rmforce -Full 이름 rmforce 개요 rm -Recurse -Force $path 구문 rmforce [[-path] ] [] 매개 변수 -path 파일, 디렉토리 경로 필수 여부 false 위치 1 기본값 파이프라인 입력 적용 여부 false 와일드카드 문자 적용 여부 false 이 cmdlet은 Verbose, Debug, ErrorAction, ErrorVariable, WarningAction, WarningVariable, OutBuffer, PipelineVariable 및 OutVariable과 같은 일반 매개 변수를 지원합니다. 자세한 내용은 about_CommonParameters(http://go.microsoft.com/fwlink/?LinkID=113216)를 참조하십시오. 입력 출력 -------------------------- 예 1 -------------------------- PS C:\>rmforce aaa


<br/>

#### 예제 : 모든 기능을 다 사용한 풀버전 function
> 입력파라미터의 강타입선언, cmdlet바인딩, 파이프라인적용, help주석 등
> 모든 기능을 전부 사용해서 만든 예제
> 
> 하지만 기능은 별거 없다.
> 프로세스 리스트를 prefix, 이름, 프로세스ID만 선택하는 기능이다.
```powershell
<#
.SYNOPSIS
    시놉시스
.PARAMETER prefix
    프레픽스
.EXAMPLE
    PS C:\WINDOWS\system32> $DebugPreference = "continue"
    PS C:\WINDOWS\system32> ps | select -First 5 | getcustomprocessinfo -prefix 프레픽스 -strArray @("하나", "둘", "셋")
    디버그: strArray.Length : 3
    디버그: idx : 0
    디버그: randValue : 하나

    디버그: strArray.Length : 3
    디버그: idx : 1
    디버그: randValue : 둘
    디버그: strArray.Length : 3
    디버그: idx : 1
    디버그: randValue : 둘
    디버그: strArray.Length : 3
    디버그: idx : 1
    디버그: randValue : 둘
    디버그: strArray.Length : 3
    디버그: idx : 2
    디버그: randValue : 셋
    prefix   ProcessName          ProcessId randValue
    ------   -----------          --------- ---------
    프레픽스 ApplicationFrameHost     14912 하나
    프레픽스 ccSvcHst                  3508 둘
    프레픽스 chrome                    2220 둘
    프레픽스 chrome                    3076 둘
    프레픽스 chrome                    6288 셋
#>
function getcustomprocessinfo
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelinebyPropertyName=$true)]
        [System.Diagnostics.Process]
        $process,

        [System.String]
        $prefix,

        [System.String[]]
        $strArray
    )
    begin
    {
    }
    process
    {
        $obj = New-Object -typename PSObject

        $obj | Add-Member -MemberType NoteProperty -Name prefix -Value $prefix
        $obj | Add-Member -MemberType NoteProperty -Name ProcessName -Value $process.Name
        $obj | Add-Member -MemberType NoteProperty -Name ProcessId -Value $process.Id

        Write-Debug ("strArray.Length : " + $strArray.Length)

        if ($strArray.Length -gt 0) 
        {
            $rand = New-Object -TypeName System.Random
            $idx = $rand.Next($strArray.Length)

            Write-Debug ("idx : " + $idx)

            $randValue = $strArray[$idx]

            Write-Debug ("randValue : " + $randValue)

            $obj | Add-Member -MemberType NoteProperty -Name randValue -Value $randValue
        }

        Write-Output $obj
    }
}



PS C:\temp> ps | where {$_.Name -eq "chrome"} | mycomplexfunc -prefix "프레픽스" -strArray @("일", "이", "삼")
디버그: strArray.Length : 3
디버그: idx : 2
디버그: randValue : 삼

디버그: strArray.Length : 3
디버그: idx : 2
디버그: randValue : 삼
디버그: strArray.Length : 3
디버그: idx : 0
디버그: randValue : 일
디버그: strArray.Length : 3
디버그: idx : 0
디버그: randValue : 일
디버그: strArray.Length : 3
디버그: idx : 1
디버그: randValue : 이
prefix   ProcessName ProcessId randValue
------   ----------- --------- ---------
프레픽스 chrome           1340 삼
프레픽스 chrome           7004 삼
프레픽스 chrome          10656 일
프레픽스 chrome          14808 일
프레픽스 chrome          21212 이




profile.ps1

이 모든 function 들과 이외의 수많은 유용한 function들을 파워쉘 세션마다 로드되게 하고 싶으면 profile.ps1 파일을 수정하면 된다.

이 파일의 위치는 아래와 같다. C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1

아래 파일을 다운로드받고 독자들 각각의 환경에 맞추어 하드코딩된 파일경로들을 수정하길 바란다. https://raw.githubusercontent.com/HyundongHwang/PsDev/master/PsScripts/profile.ps1




외부모듈 사용

사실 파워쉘의 함수들은 닷넷dll을 모두 사용할 수 있으므로 필요한 기능이 아무리 복잡해 보여도 파워쉘 구문을 꿰뚫고 있으면 그냥 스크립트만 가지고 모두 파워쉘 function 내부에 구현하면 된다. 하지만 필자가 실제로 파워쉘 스크립트 코딩을 하다보니 코드자동완성기능 빈약, 컴파일 없음, 정지점 디버깅 안됨 등 스크립트코딩의 태생적인 한계때문에 조금 복잡한 기능이 될것 같으면 그냥 커스톰 닷넷 dll만들어서 버리고 그 기능 파워쉘에서 사용하거나 이 모듈들을 스크립트로 접착해서 사용하는게 더 낫다는 생각에 이르렀다.

step1. 닷넷 라이브러리 dll 구성

먼저 닷넷 라이브러리 dll을 만들어서 파워쉘홈 디렉토리에 배포한다. C:\Windows\System32\WindowsPowerShell\v1.0\

using System.IO.Compression;

namespace HPsUtils
{
    public static class HPsUtils
    {
        public static void Zip(string dir, string zipFile)
        {
            try
            {
                ZipFile.CreateFromDirectory(dir, zipFile);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
        
        public static string DoubleStr(string str)
        {
            return str + str;
        }
}


step2. 라이브러리 사용등록

profile.ps1 파일에 커스톰dll의 사용등록을 한다. 필자는 파워쉘홈 디렉토리에 dll을 복사해 두고 세션시작시마다 등록해서 사용한다.

if (Test-Path "C:\hhdcommand\PsDev\HPsUtils\bin\Debug\HPsUtils.dll")
{
    Write-Debug "HPsUtils.dll 업데이트 ..."
    cp -Force C:\hhdcommand\PsDev\HPsUtils\bin\Debug\HPsUtils.dll $PSHOME
    Add-Type -Path "$PSHOME\HPsUtils.dll"
}
else
{
    Write-Debug "HPsUtils.dll 업데이트 스킵 ..."
}


step3. 라이브러리 실제 사용

mytestdir 디렉토리를 만들고 여기에 난수로 100개의 파일을 만든다. 이후 닷넷 압축함수를 이용하여 파워쉘에서 압축을 수행한다.

zip, unzip 함수 말고도 다른 함수가 있는지 help 함수를 만들어 두었다. help 함수는 reflection을 사용하여 전체 함수의 리스트를 리턴하여 도움말과 같은 기능을 한다.

PS C:\temp> mkdir mytestdir



PS C:\temp> for($i=0; $i -lt 100; $i++){ $rand = Get-Random; $rand > "mytestdir\$rand.txt" }



PS C:\temp> ls .\mytestdir\ | select -First 10
    디렉터리: C:\temp\mytestdir
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----     2016-02-04   오후 9:42             24 100781744.txt
-a----     2016-02-04   오후 9:42             26 1012567270.txt
-a----     2016-02-04   오후 9:42             26 1033485104.txt
-a----     2016-02-04   오후 9:42             26 1050876069.txt
-a----     2016-02-04   오후 9:42             26 1068604014.txt
-a----     2016-02-04   오후 9:42             26 1112232718.txt
-a----     2016-02-04   오후 9:42             26 1113412666.txt
-a----     2016-02-04   오후 9:42             26 1121581891.txt
-a----     2016-02-04   오후 9:42             26 1176129291.txt
-a----     2016-02-04   오후 9:42             26 1206878537.txt



PS C:\temp> [HPsUtils.HPsUtils]::help()
ReturnType FuncName  ParamList
---------- --------  ---------
List`1     help      {}
Void       Zip       {String dir, String zipFile}
Void       Unzip     {String dir, String zipFile}
String     DoubleStr {String str}
Type       GetType   {}



PS C:\temp> [HPsUtils.HPsUtils]::Zip("$pwd\mytestdir", "$pwd\my.zip")



PS C:\temp> ls
    디렉터리: C:\temp
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----     2016-02-04   오후 9:42                mytestdir
-a----     2016-02-04   오후 9:44          12665 my.zip


step4. 파이프라인 사용하여 연결

파이프라이닝 예제를 위해서 문자열을 복제해서 붙여주는 DoubleStr 함수를 만들었다. a, b, c 3개의 문자열 배열을 준비해서 다음파이프로 보내고, 파이프에 도착한 문자열을 루프돌면서 더블치고 다음파이프를 연결하면 계속 파이프라이닝 코딩을 할 수 있다.

PS C:\WINDOWS\system32> @("a", "b", "c") | % { [HPsUtils.HPsUtils]::DoubleStr($_) } | % { [HPsUtils.HPsUtils]::DoubleStr
($_) }
aaaa
bbbb
cccc




예제모음 git

이제까지 설명한 예제는 git으로 오픈되어 있다. https://github.com/HyundongHwang/PsDev

독자여러분은 연습삼아 git clone 조차 파워쉘에서 실습해 보기 바란다.

PS C:\temp> git clone git@github.com:HyundongHwang/hhdcommand.git hhdcommand
Cloning into 'hhdcommand'...
remote: Counting objects: 1003, done.
remote: Compressing objects: 100% (16/16), done.