Help us understand the problem. What is going on with this article?

RPAを検討する人に知ってもらいたい「JenkinsでのWindows自動化」

More than 1 year has passed since last update.

はじめに

「RPA以外にも自動化の選択肢はあるんやで」という記事です。
世の中、RPAに代表されるようなノンプログラミングツールでの自動化が流行り初めていますが、その流れに逆らうように、プログラミングでの自動化について記載したのが本記事です。
Windows端末で諸々自動化したので、その際に作ったJenkins環境を忘れないように具体例を残しておきます。

本記事のJenkinsが提供する機能
以下の5つです。それ以外にもプラグインなどで拡張すればきっとだいたいできるっすよ。
(GUIのみのwindowsクライアントツールの自動化は苦手な気がするので、それはRPAの方が得意そうな気がする。)

  • Excelマクロ操作
  • ファイルサーバーへのアップロード/ダウンロード
  • ブラウザ操作
  • windows-linux間のファイル転送
  • linuxサーバーでの処理

イメージ図
image.png

環境構築編

準備

3Stepなので、誰でも簡単!

  1. JDKをインストール
  2. jenkinsのwarをダウンロード
    • Downloadページ>LTS>Generic Java Package(.war)
  3. jenkinsをjdkで起動する
    • 起動オプションについては、後述します。

補足1:Javaのバージョン
最新情報は以下で確認して欲しいですが、記事記載時点では、Java8です。最新のJava9だと動かないっす。
Jenkins: Getting Started with the Guide

補足2:Windowsインストーラは使わない
WEBブラウザの操作を自動化する際、serviceから起動していると、WEBdriverが起動しない。
ブラウザ操作の自動化が不要な場合は、インストーラーでも良いかも。
参考:WindowsでJenkinsとWebDriverを動かそうとしてハマった

補足3:JREではなくJDKからの起動
この記事では記載しないですが、mavenでビルドする際に、jdkであることが要求されるので。
設定で後から追加もできますが、多少の手間も減らすため、JDKからの起動としています。

初期設定

起動後、一番最初に以下にアクセスすると、初期設定を求められます。
http://localhost:8080/

基本的には画面に言われるがままにやれば良いです。
ここらへんは記事も多いと思うので、割愛。
参考:[Jenkins] Windows7にJenkinsをインストールする方法まとめ

機能拡充編

Excelマクロ

事前準備
1. powershellプラグインを入れる

ジョブの設定
1. Jenkinsにて「ビルド手順の追加」で「Windows Powershell」を追加
2. 以下のコードを打ち込む
※「ビルドのパラメータ」で「xls_macro_file」と「xls_macro_function」を用意しています。
 Excelマクロに引数が引き渡せないですが、まぁひとまず十分な感じ。

Jenkins側のpowershellコード
C:\rpawk\common\bin\execxlsmacro.ps1 $env:xls_macro_file $env:xls_macro_function
execxlsmacro.ps1
Param( $macroBook, $procedureName ) # 引数の取得

# 起動するファイルを標準出力
$macroBook

# ファイルパスの取得
$macroBookFullPath = (ls $macroBook).FullName
$macroProcName = (ls $macroBook).Name + "!" + $procedureName

# 起動するマクロを標準出力
$macroProcName

# Excelの起動
$excelApp = New-Object -com "Excel.Application"

try {
  # マクロの起動
  $excelApp.DisplayAlerts = $false
  $excelApp.Workbooks.Open($macroBookFullPath)
  $excelApp.Run($macroProcName)
} finally {
  # Excelの終了
  $excelApp.EnableEvents = $false
  $excelApp.DisplayAlerts = $false
  $excelApp.Visible = $false
  $excelApp.Workbooks | % { $_.Close($false) }
  $excelApp.Quit()
}

補足1:Excelマクロでのパラメータ(環境変数)の取得
以下のようにすれば、「ビルドのパラメータ化」で指定したパラメータを取得できます。

Excelマクロ
environmentString = Environ("パラメータ名")

補足2:引数を指定したい場合

Jenkins側のpowershellコード
$excelApp = New-Object -com "Excel.Application"
$macroBook = $excelApp.Workbooks.Open("マクロブックへの絶対パス")
try {
  $excelApp.Run("'$($macroBook.name)'!マクロ名", "引数1", "引数2")
} finally {
  $excelApp.EnableEvents = $false
  $excelApp.DisplayAlerts = $false
  $excelApp.Visible = $false
  $excelApp.Workbooks | % { $_.Close($false) }
  $excelApp.Quit()
}

参考:JenkinsでVBAを動かす際のあれこれ
 →本記事で触れていない注意事項等も丁寧に記事化されていますので、ぜひご一読を。

ファイルサーバーへの転送

事前準備

  1. powershellプラグインを入れる
  2. 「Jenkinsの管理>システム設定>グローバル プロパティ>環境変数」に以下パラメータを用意する
    • filesv_user:ファイルサーバーにログインする際のユーザー(ドメイン\ユーザー)
    • filesv_pass:ファイルサーバーにログインする際のパスワード
    • drivepath:ファイルサーバーの最初のディレクトリのフルパス(ネットワークドライブに指定するため)

ジョブの設定
1. 以下に記載した「local2server.ps1」および「server2local.ps1」をテキトーな場所に置く
2. Jenkinsにて「ビルド手順の追加」で「Windows Powershell」を追加
3. Jenkinsから以下コードを呼び出す。

※「ビルドのパラメータ」で「source 」と「destination」を用意しています。

アップロード

Jenkins側のpowershellコード
C:\rpawk\common\bin\local2server.ps1 $source $destination
local2server.ps1
#引数取得
Param( $source, $filesv_copy_target )

# ユーザ名
$user = $env:filesv_user

# パスワード
$spass = ConvertTo-SecureString $env:filesv_pass -AsPlainText -Force

# PSCredential(資格情報)オブジェクトを作成
$cred = New-Object System.Management.Automation.PSCredential($user, $spass)

# PSCredentialオブジェクトを使ってこのスクリプトからアクセス可能なPSDriveを作成
New-PSDrive -Name Fsv -PSProvider FileSystem -Root $env:drivepath -Credential $cred

#PSDrive経由でのアクセス
if ($filesv_copy_target.Contains($env:drivepath)) {
  $destination = "Fsv:" + ($filesv_copy_target -replace [regex]::Escape($env:drivepath), "")
}

# コピーを実行
$outputs = Copy-Item -Path $source -Destination $destination -Force -PassThru

# コピーしたファイルと件数を表示
$outputs
$outputs.Count

ダウンロード

Jenkins側のpowershellコード
C:\rpawk\common\bin\server2local.ps1 $source $destination
server2local.ps1
#引数取得
Param( $source, $destination )

# ユーザ名
$user = $env:filesv_user

# パスワード
$spass = ConvertTo-SecureString $env:filesv_pass -AsPlainText -Force

# PSCredential(資格情報)オブジェクトを作成
$cred = New-Object System.Management.Automation.PSCredential($user, $spass)

# PSCredentialオブジェクトを使ってこのスクリプトからアクセス可能なPSDriveを作成
New-PSDrive -Name Fsv -PSProvider FileSystem -Root $env:drivepath -Credential $cred

#PSDrive経由でのアクセス
if ($source.Contains($env:drivepath)) {
  $source_path = "Fsv:" + ($source -replace [regex]::Escape($env:drivepath), "")
}

# コピーを実行
$outputs = Copy-Item -Path $source_path -Destination $destination -Force -PassThru

# コピーしたファイルと件数を表示
$outputs
$outputs.Count

参考:JenkinsでPowerShellを使ってリモートの(Windows, AD)サーバへファイルをコピーするには.スクリプト内で資格情報オブジェクトを作ってそれを使ってサーバにアクセスする.

ブラウザ操作

事前準備
1. pythonプラグインを入れる
2. webdriverを入手する(参考:Python で Selenium WebDriver を使ったブラウザテストをする方法
3. pip install serenium
※python for windows も必要かも。

ジョブの設定
1. Jenkinsにて「ビルド手順の追加」で「Execute python」を追加
2. 以下のコードを打ち込む

Jenkins側のpythonコード
#!/usr/bin/python
# coding: utf-8
import os
from selenium import webdriver
from selenium.webdriver.support.ui import Select

# 処理が早すぎて要素が取得できない場合、この数値を調整する。
sleeptime=5
dlsleeptime=30

# driver起動
driver_options = webdriver.ChromeOptions()
driver_options.add_argument('--no-sandbox')
driver = webdriver.Chrome("C:/rpawk/common/bin/chromedriver.exe", chrome_options=driver_options)
driver.implicitly_wait(10) # 要素が見つからなかった場合のポーリング期間(秒)

# 処理
# (割愛)

# 完了
driver.close()

参考:Python3 と Selenium Webdriver に挑戦

補足1:pythonでのパラメータ(環境変数)の取得
以下のようにすれば、「ビルドのパラメータ化」で指定したパラメータを取得できます。

python
var=os.environ['パラメータ名']

windows-linux間のファイル転送

事前準備
1. WinSCPをインストールしておく
2. pythonプラグインを入れる
3. tereterm等で該当サーバーにアクセスしておいて、既知のホストに設定しておく。

ジョブの設定
1. 以下に記載した「local2server.winscp」および「server2local.winscp」をテキトーな場所に置く
2. Jenkinsにて「ビルド手順の追加」で「Windows Powershell」を追加
3. Jenkinsから以下コードを呼び出す。

アップロード

Jenkins側のwindows.batコード
"C:\Program Files (x86)\WinSCP\WinSCP.exe" /console /script="C:\rpawk\common\scpscript\upload.txt" /parameter %upload_server% %source% %destination%
upload.txt
option batch on
option confirm off
open %1%
cd %3%
option transfer binary
put %2%
close
exit

ダウンロード

Jenkins側のwindows.batコード
"C:\Program Files (x86)\WinSCP\WinSCP.exe" /console /script="C:\rpawk\common\scpscript\download.txt" /parameter %download_server% %source% %destination%
download.txt
option batch on
option confirm off
open %1%
lcd %3%
option transfer binary
get %2%
close
exit

補足1:server名
以下記載でログインできる。

user:password@hostname

補足2:「sudo を実行するには tty がなければいけません。すみません。」
ptyにチェックを入れましょう。
ただ、sudo su -は機能しません。(ログインし直すせい?)
sudo su -の実行に関しては、teretermマクロを起動するくらいしか、今のところは思いつきません。。。

Jenkins側のwindows.batコード
%ProgramFiles(x86)%\teraterm\ttpmacro.exe” <tllマクロファイルフルパス> [引数] 

参考:TeraTermマクロをバッチファイルから実行する方法
参考:WinSCPとは
参考:WinSCPスクリプト入門:コマンドやバッチファイルとの連携による自動処理
参考:スクリプト機能 - WinSCP Wiki - WinSCP - OSDN

Linuxサーバーでの処理

ここはプラグインの機能そのままなので、あんま言うことはない。

事前準備
1. SSH Pluginをインストールしておく
2. 接続設定をしておく(参考:Jenkinsからリモートサーバーにsshする設定
3. tereterm等で該当サーバーにアクセスしておいて、既知のホストに設定しておく。

ジョブの設定
1. Jenkinsにて「ビルド手順の追加」で「リモートホストでのシェル実行」を追加

補足1:暗号化強度について
「Teratermでは繋がるのにJenkinsでは繋がらない!」みたいな時です。
sshに関するログを見ると、以下のようなエラーが出ている場合は、参考情報にあるようにAES256を使用できるようにしてあげれば解決するはずです。

/var/log/secure(中身はJENKINS-36648から引用)
Jul 11 13:09:22 server auth|security:crit sshd[21561580]: fatal: no matching cipher found: client aes128-ctr,aes128-cbc,3des-ctr,3des-cbc,blowfish-cbc server aes256-ctr,aes192-cbc,aes256-cbc [preauth]

参考:[JENKINS-36648]SSH plugin : no matching cipher found - can't connect to server
参考:JavaでAES256を使用できるようにする
参考:sshdのログファイルの確認方法

通知

ネットワーク内にRocket.chatを立てているので、そこに通知を集約しています。

OS起動・停止
WindowsUpadteによる予期せぬ起動/停止や、管理者以外の人が勝手にOS起動/停止しちゃった場合に気づくため。

  1. ファイル名を指定して実行>gpedit.msc
  2. ローカル コンピューター ポリシー>コンピュータの構成>Windows の設定>スクリプト(スタートアップ/シャットダウン)
  3. 通知処理を呼び出すように設定
起動時呼び出しコマンド
C:\rpatools\client_start.bat
停止時呼び出しコマンド
C:\rpatools\client_shutdown.bat
client_start.bat
python "C:\rpawk\common\bin\notificationToRocketChat.py" "client_start"
client_shutdown.bat
python "C:\rpawk\common\bin\notificationToRocketChat.py" "client_shutdown"
notificationToRocketChat.py
# encode: utf-8

#import
import requests
import json
import sys

def post_text(token, bodyText):
  h = {'Content-Type': 'application/json'}
  baseurl = "ロケットチャットのURL/hooks/"
  payload = {'text': bodyText}

  # debug用情報
  print("token:" + token)

  # post
  requests.post(baseurl + token, headers=h, data=json.dumps(payload))

def mainfuncion()
  args = sys.argv
  args_str = ','.join(args)

  # debug用情報
  print("args:" + args_str)

  # message処理のエラー
  token = "エラー通知する先のトークン"
  bodyText = ":question:typeが判別できませんでした。 args:" + args_str

  type = args[1]
  if type=="client_start":
    token = "起動通知する先のトークン"
    bodyText = ":thinking:jenkinsクライアント端末が起動しました。必要に応じて、ソフトを立ち上げてください。"

  elif type=="client_shutdown":
    token = "停止通知する先のトークン"
    bodyText = ":zzz:jenkinsクライアント端末をシャットダウンします。"

  # post
  post_text(token, bodyText)

if __name__ == "__main__":
  mainfuncion()

参考:Windows、PC起動時に任意のバッチファイルを自動実行する
参考:[Jenkins][管理者向け] 起動時トリガージョブ

定期的なエラーチェック
以下を指定したジョブを5分おきに流しています。(rocket.chatプラグインありますが、なぜか動かないので、自作。)
こんな感じで通知してくれる。
「落ちてまっせ」の右側は、対象jenkinsのURL
その下は、ジョブ名が一覧になっており、「 -ジョブ名(ジョブのURL)」になっています。
image.png

Jenkins側のWindowsバッチスクリプト
python "C:\rpawk\common\bin\jenkinsErrorCheck.py" "rocket.chatのトークン" "サーバAのURL" "○○用jenkins"
python "C:\rpawk\common\bin\jenkinsErrorCheck.py" "rocket.chatのトークン" "サーバBのURL" "☓☓用jenkins"
errorCheck.py
#!/usr/bin/python
# coding: utf-8

import requests
import json
import sys
import os
sys.path.append("C:\\rpawk\\common\\bin")
import notificationToRocketChat
USERNAME="ユーザー名"
PASSWORD="パスワード"

def get_before_timestamp():
  jenkins_url = os.getenv("JENKINS_URL")

  # apiで取得
  p = {'depth': '2', 'tree': 'jobs[displayName,buildable,lastCompletedBuild[number,timestamp,result,url,duration]]'}
  r = requests.get(jenkins_url + "api/json", auth=(USERNAME, PASSWORD), params=p)
  print(str(r))
  # print(str(r.json()["jobs"]))

  beforetime = [i["lastCompletedBuild"]["timestamp"] for i in r.json()["jobs"] if i["displayName"]==os.getenv("JOB_NAME")][0]
  print("beforetime: " + str(beforetime))
  return beforetime

def get_builds(jenkins_url, beforetime):
  # apiで取得
  p = {'depth': '2', 'tree': 'jobs[displayName,buildable,lastUnsuccessfulBuild[number,timestamp,result,url,duration]]'}
  r = requests.get(jenkins_url + "api/json", auth=(USERNAME, PASSWORD), params=p)
  print(str(r))
  # print(str(r.json()["jobs"]))

  # 前回実行後にエラーになったものだけを抽出
  result_datas = [i for i in r.json()["jobs"] if i["lastUnsuccessfulBuild"] is not None and i["lastUnsuccessfulBuild"]["result"]!="SUCCESS" and int(i["lastUnsuccessfulBuild"]["timestamp"]) >= beforetime ]
  print("result: " + json.dumps(result_datas))
  return result_datas

def mainfunction(token, jenkins_url, server_name):
  builds_datas = get_builds(jenkins_url, get_before_timestamp())

  if builds_datas:
    print("Error!!!")
    joblist = "\n -" + "\n -".join([i["displayName"] + " ( " + jenkins_url + "job/" + i["displayName"] + " )" for i in builds_datas])
    textdata = u":rage:落ちてまっせ: " + jenkins_url + " (" + server_name + ")" + joblist
    notificationToRocketChat.post_text(token, textdata)

  else:
    print(u"Not Error. Luky!")

if __name__ == "__main__":
  argvs = sys.argv  # コマンドライン引数を格納したリストの取得
  argc = len(argvs)
  print(str(argvs))  # デバッグプリント
  print(str(argc))  # デバッグプリント
  if (argc != 4):   # 引数が適切でない場合は、その旨を表示
    print('Erro: invalid argument')
    print('Usage: python %s ROCKET_CHAT_TOKEN JENKINS_URL JENKINS_SERVER_NAME' % argvs[0])
    sys.exit(1)         # プログラムの終了

  mainfunction(argvs[1], argvs[2], argvs[3])

補足1:slackへの通知
slackへの通知は、Jenkinsおじさんビルド結果Slackに通知してを見ると良いかと。

見栄え

いずれも、ログインした後の見た目が変わります。(ログイン画面変えたいけど、war弄るぐらいしか思いつかない。)

テーマ

何はともあれSimpleThemePlugin

DLして使用する場合は、以下Step。
1. %JENKINS_HOME%/userContenttheme.css,theme.js,icon.png(名前はテキトー)を格納
2. Jenkinsの管理>システム設定>Themeで/userContent/theme.css/userContent/theme.jsを指定する。
3. Jenkinsの管理>システム設定>Faviconで/userContent/icon.pngを指定する。

左上おじさんに関しては、theme.cssに追記する。
タイトルに関してはtheme.jsを作成する。

theme.css
#jenkins-head-icon {
content:url('/userContent/icon.png');
height:36px;
width:auto;
}
#jenkins-name-icon {
content:url('/userContent/logo.png');
height:36px;
width:auto;
}
theme.js
window.addEventListener("load", function() {  
  var title = document.querySelector('title');
  title.innerHTML = title.innerHTML.replace(/\[Jenkins\]/, '[Shiro-iruka]');
}, false)

参考:JenkinsのUIをカスタマイズする
参考:Jenkinsの左上のおじさんアイコンを変更する
参考:フリーフォントで簡単ロゴ作成

運用編

起動オプション

ご参考程度にどうぞ。
少なくともメモリは設定したほうが良いかと。1GBを超えると起動しなくなったので、1GBが限界?
セキュリティ的に推奨されない指定もしているので、各自ご調整ください。

jenkinsStart.bat
SET BASE=C:\rpatools\jenkins
C:\java\jdk\bin\java -Xrs -Xmx1024m -Xms1024m -XX:MaxMetaspaceSize=256m -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=8090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -verbose:gc -Xloggc:"gc.log" -XX:HeapDumpPath="heapdump.log" -XX:ErrorFile="javaerror.log" -Dhudson.lifecycle=hudson.lifecycle.WindowsServiceLifecycle -jar "%BASE%\jenkins.war" --httpPort=8080 --webroot="%BASE%\war" >> %JENKINS_HOME%\logs\jenkins.log 2>>&1

参考:Jenkinsの起動オプションを細かく指定して起動

起動・停止

手動で起動/停止でございます。batファイルでも作っておきましょう。
停止は、Ctrl+Cとか、タスクマネージャーからのプロセスダウンとかで。
SafeRestartは機能しません。。。

ちなみに、起動手段と、機能の使える使えないのマッピングはこんな感じ。

起動手段 ブラウザ操作 Excelマクロ起動
Service :no_entry_sign: :question:(試してない)
タスクスケジューラ :thumbsup: :no_entry_sign:
手動 :thumbsup: :thumbsup:

アップデート

Jenkinsのバージョンを上げたいときは、WARを置き換えれば良いんやで。
不安なら%JENKINS_HOME%をバックアップしておきましょう。

最後に

RPAのもっとも特徴的なポイントはノンプログラミングで色々作れる点だと思います。
事務員さんが多い会社さんとか、プログラムの素養がある人が少ないところでは、今まで外注しないとできなかったことが自分で作れるツールなので、そこに非常に効果的なのでは?と感じています。
(他にも、自動化の実装が簡易とか、メリットは多いです。)

ただ、プログラマーの多い環境であれば、Jenkinsとかを用いて自動化を図ったほうが、コードに触れる機会が増えるので良いのでは?と思っています。
無償だし、並列実行もできるし、管理機能も充足しているし、情報もたくさん落ちているし、無料だし、無料だし、無料だし、文句なしだと思う。
(面倒な決済を取らなくて済むのが本当に嬉しい)

少し脱線しましたが、詰まるところ、自動化とか効率化のためのツール導入ってのは、「そのツールを誰が使うのか?」を中心にして、検討をしたほうが良いと思っています。
自動化する作業って、係数取得的なものが多かったりする気がするので、それをやる人は誰なんだろうか・・・って話ですね。
プログラマがそのツールを使うのであれば、RPAと合わせて、Jenkinsでの自動化も選択肢に入れてみてはいかがでしょうか。

nh321
せやかて工藤、このアカウントが発信するんは全て個人的な意見で、現在所属する会社の公式見解では無い、ゆーとるやろが。
tis
創業40年超のSIerです。
https://www.tis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした