RedPlug's Tory

[Powershell] 함수1

Windows2020. 11. 21. 21:46

요새 공부를 하고 있긴 한데 정작 파워쉘쪽에 좀 소홀 했던것 같아서 

오랜만에 김도균 강사님 책 펴고 공부해봤네요~^^

어떤 언어를 해도 함수얘기는 거의 공통적으로 나오는 얘기라서 어렵지 않게 이해가 된 것 같습니다.

실무에서 바로 쓰는 파워쉘 - 김도균
9장 - 고급 스크립트 작성 기술

단순한 스크립트 작성에서 좀 더 나아가기 위한 방법인 함수에 대한 내용입니다.

함수의 기본 골격은 다음과 같습니다.

Function Verb-Noun {

     [CmdletBinding()]

     1) param()

     2) BEGIN{}

     3) PROCESS{}

     4) END{}

}

1. param

[Parameter(위치, 필수 매개변수, 파이프라인 입력)]

[데이터 형식]$변수명 = 기본값(옵션)

2. BEGIN

제일먼서 실행되는 부분이며 한번만 실행된다. 사전설정이 필요한 구문을 작성 예) 데이터베이스 초기 연결 설정, 특별한 설정이 필요 없는 경우 비워 놓는다.

3. PROCESS{}

함수의 주 로직이 들어가는 부분, 반복 처리를 하는 로직의 경우 작성 시 BEGIN쪽으로 넣어야 하는 블록을 구분하는게 좋다.(한번 실행하는걸 여러번 실행하게 되기 때문)

4. END{}

제일마지막에 실행되는 구분. 정리 작업에 해당 하는 구문(데이터베이스 연결종료 등), 별도로 필요하지 않는 다면 비워놓는다.

 

함수를 만드는 절차

  1. 함수를 function이라는 키워드와 함께 동사-명사 이름 규칙을 따라 함수 이름을 선언
  2. 함수의 블록 내부에서 처리하는 매개변수 설정을 param() 블록에 입력
  3.  주 처리 로직의 설정에 해당하는 구문을 BEGIN 블록에 입력
  4. 원하는 작업의 주 처리 로직을 PROCESS 블록에 입력
  5. 주 처리 로직의 정리에 해당 하는 작업을 END 블록에 입력

예제함수

function Get-SecurityEvents {
[CmdletBinding()]
Param(
[Parameter(Position=0,Mandatory=$True)]
[string]$ComputerName,

[int]$EventID = 4634
)   
    BEGIN 
    {
        $LogName = Read-Host "로그 이름을 입력하세요."        
    }
    PROCESS
    {
        Get-EventLog -ComputerName $ComputerName `
        -LogName $LogName |
    Where-Object -Property EventID -eq $EventID |
    Select-Object -First 7
    }
END {}
}

함수로 만들경우 바로 실행되지 않으며 함수를 실행하여 메모리에 올리고 실행하고자 하는 함수 이름을 콘솔에서 호출하여야 한다.

하기는 함수를 호출 후 확인하는 스크린샷

정상적으로 함수 호출 되었으며, 실행

파워쉘의 범위

범위 설명 범위 한정자
전역 파워셸 세션을 시작할 때 범위 $global:변수명
로컬 현재 범위, 전역 범위나 다른 범위가 될 수 있다. $local:변수명
스크립트 스크립트 파일이 실행되는 동안 만들어지는 범위 $script:변수명
전용(Private) 전용 범위의 항목은 현재 범위 외부에서 보이지 않는다 $private:변수명
숫자를 부여한 범위 숫자 범위는 한 범위와 다른 범위의 상대 위치를 나타낸다.  

범위 한정자를 활용하면 함수나 스크립트 내부의 변수에 접근하게 하거나, 함수의 실행 범위를 지정해 다른 범위에서 해당 함수를 접근하게 할 수 있다.

예제(Chap9EX2~3.ps1)

$Global:globalVar='전역 범위. 스크립트 밖에서 접근'

Function Check-FunctionScope
{
    $functionVar = '함수 범위. 함수내에서만 접근'
    "functionVar은 $functionVar"
    $Local:localVarInFunc='함수내에서 선언한 로컬 범위'
    $Script:scriptVarInFunc='함수내에서 선언한 스크립트 범위'
    $Global:globalVarInFunc='함수내에서 선언한 전역 범위'
    ''
    CallFrom-Function
}
''

$scriptVar = '스크립트 범위. 스크립트 내에서만 접근'
"scriptVar은 $scriptVar"
''

Check-FunctionScope
''
CallFrom-ScriptFunction global:CallFrom-Function
{
    $callByFunction = '다른 함수에서 불렀어요.'
    "callByFunction 변수는 $callByFunction"
}

Function global:CallFrom-Script
{
    $callByScript = '다른 스크립트에서 불렀어요.'
    "callByScript 변수는 $callByScript"
}

$Global:globalVar='전역 범위. 스크립트 밖에서 접근'

Function Check-FunctionScope
{
    $functionVar = '함수 범위. 함수내에서만 접근'
    "functionVar은 $functionVar"
    $Local:localVarInFunc='함수내에서 선언한 로컬 범위'
    $Script:scriptVarInFunc='함수내에서 선언한 스크립트 범위'
    $Global:globalVarInFunc='함수내에서 선언한 전역 범위'
    ''
    CallFrom-Function
}
''

$scriptVar = '스크립트 범위. 스크립트 내에서만 접근'
"scriptVar은 $scriptVar"
''

Check-FunctionScope
''
CallFrom-Script

3번스크립트 실행 후 2번 스크립트 실행

 

예제 : Chap9EX4.ps1

function Send-OutlookMail{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [string] $From = "redplug@outlook.com"
        [Parameter(Mandatory=$True)]
        [string] $To,
        [Parameter(Mandatory=$False)]
        [string] $Cc = "redplug@gmail.com"
        [Parameter(Mandatory=$True)]
        [string] $Subject,
        [Parameter(Mandatory=$False)]
        [string] $Body = "PowerShell Send Mail"
    )
    BEGIN 
    {
        $Cred = (Get-Credential)  // 인증처리 부분
        $SmtpSvr="smtp.live.com" 
        $SmtpPort="587"     
    }
    PROCESS
    {
        Send-MailMessage -From $From -To $To -Cc $Cc -Subject $Subject `
        -Body $Body -SmtpServer $SmtpSvr  -Port $SmtpPort -UseSsl `
        -Credential $Cred -Encoding UTF8
    }
    END
    {
        Clear-Variable -Name Cred
    }
}

발송엔 성공했는데 안보이길래 뒤져봤더니 스펨함에 떨어졌네요.

기존에 밥집 추천을 list로 만들어둔게 있었는데 변경할 때마다 매번 코드를 수정해줘야 하는 불편함이 좀 있어서...

밥집을 수정하면 코드를 수정해야 하는 번거로움이.ㅠㅠ

DB에서 긁어오는 방법으로 변경 하기로 합니다.

우선 서버에 마리아 DB를 설치 한 후에 기존에 리스트 값을 DB에 넣어줍니다. (csv로 밀어넣음)

(bob_db database에 bob table)

필드는 number, name, url

밥집 리스트DB를 불러와서 추천해주기 위한 모듈을 하나 만듭니다.

 

모듈에서 DB에서 number를 불러와서 db사이즈 확인 후 랜덤하게 숫자를 선택후에 해당 number에 해당하는 name과 url을 가져와서 슬랙 봇에 붙여넣을 수 있게 변경 후 값을 리턴 합니다.

bob_recommend.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# -*- coding:utf-8 -*-
 
import os
 
import mysql.connector
 
import random
 
def food_answer():
    dbid = os.environ.get('DATABASE_ID')
    dbpassword = os.environ.get('DATABASE_PASSWORD')
 
    config = {
        "user": dbid,
        "password": dbpassword,
        "host""localhost"#local
        "database""bob_db"#Database name
        "port""3306" #port는 최초 설치 시 입력한 값(기본값은 3306)
    }
 
    try:
 
        conn = mysql.connector.connect(**config)
        print(conn)
        # db select, insert, update, delete 작업 객체
        cursor = conn.cursor()
        # 실행할 select 문 구성
        sql = "SELECT MAX(number) FROM bob"
        # cursor 객체를 이용해서 수행한다.
        cursor.execute(sql)
        # select 된 결과 셋 얻어오기
        resultList = cursor.fetchall()  # tuple 이 들어있는 list
        size = resultList[0][0]
        print(size)
        random_select = random.randrange(1,int(size))+1
        sql = f"SELECT name,url FROM bob where number = {random_select}"
        cursor.execute(sql)
        resultList = cursor.fetchall()  # tuple 이 들어있는 list
        result = f"<{resultList[0][1]}|*{resultList[0][0]}*>"
        return result
 
    except mysql.connector.Error as err:
 
        print(err)
 

마리아 디비 땡겨오는걸 어디선가 긁어왔는데 출처를 저장을 안해놨네요. (__);

메인 파일에서 모듈 불러오고, 함수 변경

 

정상적으로 작동 확인

 

슬랙봇 : github.com/redplug/slackbot

Jira에서 발송되는 메일큐가 쌓일 경우에 Slack으로 알람이 오도록 설정하는 코드입니다.

마켓 플레이스에 xml로 볼 수 있게끔 만들어진 앱이 있어서 해당 앱을 설치 후 이용하였습니다.

 

Atlassian Marketplace Mail Queue Monitor REST API (Free)

marketplace.atlassian.com/apps/1214195/mail-queue-monitor-rest-api?hosting=server&tab=overview

 

Mail Queue Monitor REST API

Ever had outgoing mail fail unnoticed? Monitor your error queue to detect issues before your users do

marketplace.atlassian.com

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import requests
import xml.etree.ElementTree as ET
 
# 웹훅 URL 주소
webhookurl = "Webhook URL"
 
# jira URL 주소
jiraurl = 'https://jira.redplug.com/rest/mailqueue/1.0/status'
 
# 알람 체크 갯수
notisize = 100 
 
# 웹훅 발송 함수
def send_message_to_slack(text):
   payload = {"text": text}
   requests.post(webhookurl, json=payload)
 
 
# 메일큐 확인
response = requests.get(jiraurl)
status = response.status_code
text = response.text
root = ET.fromstring(response.text)
size = int(root.find('size').text)
errorsize = int(root.find('errorSize').text)
 
# notisize 넘을경우 웹훅 
if size > notisize or errorsize > notisize:
   send_message_to_slack(f'JIRA Mail Queue Check \n Mail Queue Size : {size}개 \nMail Error Queie Size : {errorsize}개')
else:
   pass
 

slack 발송 테스트(notisize -1로 설정 후 실행)

 

현재 슬랙봇 만드는 과정은

노트북 Pycham으로 작업 > 커밋후 github로 푸시 > github에서 Jenkins로 webhook > jenkins에서 AWS로 pull > AWS에서 슬랙봇 가동의 형태로 이루어 지고 있습니다. (github 푸시 이후로 자동)

사실 처음에는 공부했던 파이썬을 어디 써먹을데 없나 하다가 예전부터 슬랙봇을 만들어보고 싶다라는 생각으로 시작했는데 생각보다 뭔가 하나씩 늘어가는 느낌이라 주말에 소소한 재미가 되고 있네요.

오늘은 github에 올리고 있던 슬랙봇 레파지토리를 공개로 돌려 보려고 합니다.

예전부터 퍼블릭으로 하려고 했는데 현재 작성하고 있던 파이썬 코드에는 슬랙봇 ID, 토큰, 네이버 클라이언트 ID, Secret 4가지가 포함이 되어있다보니 공개를 못하고 있었는데, 찾아보니 리눅스의 환경변수를 이용해서 가져오는 방법이 있어 해당 방법을 적용하였습니다.

우선

vi ~/.bashrc로 들어가서 최 하단에 export로 변수를 추가해줍니다. 

이후 source ~/.bashrc를 실행해서 export 명령어를 적용시키고

export 로 적용된 변수를 확인합니다.

이후 실제 작성하고 있던 코드상에 값들을 변수 값으로 대체를 합니다.

import os 추가 후

각 변수 별로 os.environ.get('export변수명') 으로 대체

코드 상에서 값들은 모두 삭제를 하였고

github에 기존 이력이 남아있기 때문에 히스토리를 삭제 해야 합니다.

삭제하는 방법은

1. 작업 폴더에서 .git 폴더를 삭제, 리눅스의 경우 rm -rf .git

2. 깃 초기화

git init

 

3. 초기화할 파일 추가 후 최초 커밋

git add .

git commit -m "first commit"

 

4. 저장소 연결 후 푸시

git remote add orign <github url>

git push -u --force origin master

github URL은 레파지토리 > code에서 확인 가능

 

github에서 히스토리 삭제 확인

이후 해당 레파지토리 > Settings > 최하단에 Change repository visibility에서 Make public 선택하고 하단에 레파지토리명 입력 후에 변경 처리 하면됩니다.

 

 

업무를 하던 도중 주소줄여서 적어야 하는 케이스가 간혹 생겨 귀찮이즘에 슬랙봇을 만들어야겠다! 라는 생각을 합니다.

주소 단축사이트에서 웹크롤링을 이용해서 가져오려고 했는데 뭔가 불필요한 부분이 많은것 같아서 API를 찾아보던 중 네이버에서 제공 하는 API가 있어서 그걸 이용하여 만들어 보았습니다.

 

url 단축 API를 사용하기 위해서는 네이버 개발자 센터에 계정등록 > 애플리케이션 등록 > 클라이언트 아이디와 시크릿 값을 받아와야 합니다.

developers.naver.com/docs/utils/shortenurl/

 

단축 URL API 적용 가이드

단축 URL API는 원본 URL을 `http://me2.do/example`과 같은 형태의 짧은 URL로 반환하는 RESTful API입니다.

developers.naver.com

상기 주소에 사전 준비사항 참고하여 필요한 값을 생성합니다.

Client ID, Client Secret 값을 우선 만들어둡니다.

 

 

Slackchatbot.py

슬랙 봇에서 필요한 부분만 따왔습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from urlshort import url_short
        elif event_message.find(surl) > -1:
            channel = slack_event["event"]["channel"]
            orignalurl = event_message.replace("주소단축 ","")
            print(surl)
            if event_message.replace(" ",""== surl:
                slack.chat.post_message(channel, f"주소단축 뒤에 주소를 넣어주시면 여울이가 줄여줄게요")
 
            elif event_message != surl:
                ssurl = url_short(orignalurl)
                username = '여울이가 주소를 줄여줘요!'
                icon_emoji = ''
                attachement = {
                    'pretext''여울이의 ShortUrl',
                    "fallback""여울이 ShortUrl",
                    "text": f"여울이가 주소를 줄였어요.  {ssurl}\n",
                    "fields": [
                        {
                            "value""",
                            "short"False
                        }
                    ],
                    "color""good",
                }
 
                slack.chat.post_message(channel, attachments=[attachement], username=username, icon_emoji=icon_emoji)
 
            return make_response("앱 멘션 메시지가 보내졌습니다."200, )
 
 

urlshort.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# -*- coding: utf-8 -*-
 
import urllib.request
 
import json

import html
 
def url_short(orignalurl):
    replaceurl = html.unescape(orignalurl.replace("<""").replace(">""")) # 슬랙에서 링크일 경우 <> 붙여ㅓ 해당 부분 삭제
    index = replaceurl.find("|"# http를 넣지 않을경우 |이후 추가적으로 문자열이 찍혀 해당 부분 삭제하기 위함
    client_id = "AAAAA"  # 개발자센터에서 발급받은 Client ID 값
    client_secret = "BBBBB"  # 개발자센터에서 발급받은 Client Secret 값
    encText = urllib.parse.quote(replaceurl[:index]) # index위치 이후 삭제
    data = "url=" + encText
    url = "https://openapi.naver.com/v1/util/shorturl"
    request = urllib.request.Request(url)
    request.add_header("X-Naver-Client-Id", client_id)
    request.add_header("X-Naver-Client-Secret", client_secret)
    response = urllib.request.urlopen(request, data=data.encode("utf-8"))
    rescode = response.getcode()
    if (rescode == 200):
        response_body = response.read()
        shorturl = json.loads(response_body.decode('utf-8'))
        returnurl = shorturl['result']['url']
    else:
        print("Error Code:" + rescode)
 
    print(returnurl)
    return returnurl
 
 

실제 주소를 줄여주는 코드(개발자 센터 예제에서 필요한 부분만 수정하였습니다.)

 

정상적으로 값을 가져옵니다.

1. Github 에서 Access Token 생성

Github Login > Settings

 

Developer settings

Personal access tokens > Generate new token

 

Note > repo Check > admin:repo_hook Check > 최하단 Generate token 선택

생성된 토큰 복사

2. Github Jenkins에 추가
설치 시에 GitHub Plugin이 설치되있어서 별도로 설치 하지 않았으나, 혹시 미설치되어있으면 설치 필요.

(Jenkins 관리 > 플러그인 관리 > 설치 가능 > Filter에서 Github Plugin 검색하여 설치

Jenkins > Jenkins 관리

시스템 설정

Github > Add GitHub Server > GiHub Server

Name : Https://github.com/ID/

API URL : https://api.github.com

Add > Kind Secret text > Secret GitHub Token > ID 구분할 수 있는 값으로 변경 > Test Connection 해서 통과 확인 > 저장
* Github ID와 동일할 경우 등록되지 않으니 다른 값으로 저장

3. Project 생성

새로운 Item

프로젝트이름 > Freestyle project > OK

 

GitHub project 체크 >  Project Url GitHub 프로젝트

소스 코드 관리 > Git > Repository URL 레파지토리주소 > Credentials > ADD > github id/pw 입력 > 저장

curl -fsSL https://get.docker.com/ | sudo sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
ubuntu@reds-ubuntu:~$ curl -fsSL https://get.docker.com/ | sudo sh
# Executing docker install script, commit: 26ff363bcf3b3f5a00498ac43694bf1c7d9ce16c
+ sh -c apt-get update -qq >/dev/null
+ sh -c DEBIAN_FRONTEND=noninteractive apt-get install --qq apt-transport-https ca-certificates curl >/dev/null
+ sh -c curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | apt-key add -qq - >/dev/null
Warning: apt-key output should not be parsed (stdout is not a terminal)
+ sh -echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable" > /etc/apt/sources.list.d/docker.list
+ sh -c apt-get update -qq >/dev/null
+ [ -n  ]
+ sh -c apt-get install --qq --no-install-recommends docker-ce >/dev/null
+ sh -c docker version
Client: Docker Engine - Community
 Version:           19.03.13
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        4484c46d9d
 Built:             Wed Sep 16 17:02:52 2020
 OS/Arch:           linux/amd64
 Experimental:      false
 
Server: Docker Engine - Community
 Engine:
  Version:          19.03.13
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       4484c46d9d
  Built:            Wed Sep 16 17:01:20 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.3.7
  GitCommit:        8fba4e9a7d01810a393d5d25a3621dc101981175
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
If you would like to use Docker as a non-root user, you should now consider
adding your user to the "docker" group with something like:
 
  sudo usermod -aG docker your-user
 
Remember that you will have to log out and back in for this to take effect!
 
WARNING: Adding a user to the "docker" group will grant the ability to run
         containers which can be used to obtain root privileges on the
         docker host.
         Refer to https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface
         for more information.
ubuntu@reds-ubuntu:~$ 
 
 
 

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

확인

환경 : WSL2 ubuntu 20.04.1. LTS

 

JAVA 설치

## JRE 설치
sudo apt-get install openjdk-8-jre
 
## JDK 설치
sudo apt-get install openjdk-8-jdk
 

 

JAVA 버젼확인

java -version

openjdk version "1.8.0_265"
OpenJDK Runtime Environment (build 1.8.0_265-8u265-b01-0ubuntu2~20.04-b01)
OpenJDK 64-Bit Server VM (build 25.265-b01, mixed mode)
 

 

JAVA 패스 설정

## javac 위치 확인
which javac
/usr/bin/javac
 
## 실제 위치 확인
readlink -/usr/bin/javac
/usr/lib/jvm/java-8-openjdk-amd64/bin/javac
 
## PATH설정 /etc/profile 수정 
sudo vi /etc/profile
 
## /etc/profile 촤하단 추가내용
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
export PATH=$JAVA_HOME/bin/:$PATH
export CLASS_PATH=$JAVA_HOME/lib:$CLASS_PATH
 
## PATH 적용
source /etc/profile
 
## PATH 확인
echo $JAVA_HOME
/usr/lib/jvm/java-8-openjdk-amd64
 

 

 

젠킨스 설치

링크 : pkg.jenkins.io/debian-stable/

## 키 추가
wget --- https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -
 
## 젠킨스 설치 패키지 설정
sudo vi /etc/apt/sources.list
 
## source.list 최하단에 내용 추가
deb https://pkg.jenkins.io/debian-stable binary/
 
## apt update
sudo apt-get update
 
## 젠킨스 설치
sudo apt-get install jenkins
 

 

젠킨스 시작

## 젠킨스 시작
sudo /etc/init.d/jenkins start
Correct java version found
* Starting Jenkins Automation Server jenkins       [ OK ]
 

 

젠킨스 접속

기본포트 8080

http://IP주소:8080 or http://localhost:8080

패스워드 확인(하기는 임의값)

## Administrator password 확인 후 상기 페이지 입력
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
abcdefghijklmn
 

 

 

추천 선택

 

설치화면

 

관리자 계정생성

 

URL 설정

 

준비

 

설치완료

 

자동실행등록

sudo chmod +/etc/init.d/jenkins
sudo update-rc.d jenkins defaults
 

 

봇으로 간단하게 메시지 보내는 코드

반복이 필요한 경우 crontab을 이용하여 반복 실행한다

# -*- coding: utf-8 -*-
from slacker import Slacker


token = '봇토큰'
slack = Slacker (token)

slack.chat.post_message('#채널명', ":이모지:제목:이모지: \n 내용 \n https://naver.com")