2016年10月14日金曜日

共有フォルダ内のファイルやフォルダを、それを保有するPCからZIP圧縮させるスクリプト

LANで接続された2台のPCがあり、片方はPC1、もう片方はPC2という名前だったとします。
PC1はC:\testを共有フォルダとして設定済みで、PC2からはそのフォルダを参照できます。
この時、PC2からPC1の共有フォルダ内のファイルやフォルダを圧縮する場合、普通にやると水面下では以下のような処理が行われます。

1.PC1の共有フォルダからPC2に圧縮対象のデータが全てコピーされる。
2.PC2で圧縮したデータをPC1にコピーする。

合計サイズが小さいとか、ファイルやフォルダが少ない場合はこれでも問題ありませんが、大量のファイルを圧縮したい場合はネットワークでデータをやりとりする分のロスが大きくなってきます。

それを回避するスクリプトを作成しました。




[使い方]
以下のように、スクリプトファイルと同じフォルダにPsExec.exeを保存しておきます。
PsExecの入手方法はこちらをご覧ください。



圧縮したいファイルをスクリプトファイルにドラッグ&ドロップします。



セキュリティメッセージが表示された場合は「OK」をクリックします。



このメッセージが表示されたら処理終了です。OKをクリックします。



圧縮(zip形式)フォルダーができました。



ドラッグ&ドロップするものはフォルダでもファイルでも、1個でも複数個でも混在していてもOKです。

ドロップするものが保管されている場所が共有フォルダかどうかを気にする必要はありません。(スクリプトが自動で判別します)

圧縮処理にとてつもない長さの時間が必要になるようなフォルダなどを誤ってドロップしてしまった場合は処理を中断させることも可能です。

[中断する方法]
「圧縮しています...」のウィンドウが表示されている場合は、そのウィンドウの「キャンセル」をクリックしてください。


上記以外の場合は、圧縮するアイテムと同じフォルダに「remote comp_***….wsf」というファイルが作成されていますので


そのWSFファイルを削除すると


その数秒後に以下のメッセージが表示されます。(作りかけのZIPファイルは不要なら手動で削除してください)



以下、ソース
<package>
<job>
<script>
arg = WScript.Arguments
if(!arg.length){
WScript.Echo('ファイルかフォルダをドラッグ&ドロップして起動してください')
WScript.Quit()
}
main = function(){
// 全ての関数などのローディングが終了してからこの関数が実行される。
// この関数の終了=スクリプトの終了。
基準フォルダ = fs.GetParentFolderName(WScript.ScriptFullName)
pathPS = 基準フォルダ+'\\PsExec.exe'
// 引数のPathはそのまま使わずに極力ローカルPathに変換して利用する。
// 変換前と変換後の入れ物(配列)は同じ。
arrPath = []
for(var i=0,L=arg.length;i<L;i++){ arrPath[i] = arg(i) }
リモート判別()
WScript.Echo('終了')
WScript.Quit()
}
zip圧縮=function(){
var shellApp =AXO('Shell.Application')
var getNS = function(p){return shellApp.NameSpace(p)} // NameSpace = shellオブジェクトで扱う「Folder Object」を取得するメソッド
var getPFN = function(p){return fs.GetParentFolderName(p)}
// ZIPファイル作成
var path0=arrPath[0], pathZIP=getPFN(path0).replace(/([^\\\/])$/,'$1/')+fs.GetBaseName(path0)+'.zip'
if(fs.FileExists(pathZIP)){
WScript.Echo('下記PathにZIPファイルを作成しようとしましたが、同じPathのファイルが既に存在しているため、処理を中断します。\n\n'+pathZIP)
WScript.Quit()
}
Write(pathZIP,'PK'+String.fromCharCode.apply(null,'56000000000000000000'.split(''))) // ← ZIPファイルのヘッダー
// pathZIPに「/」が入っているとgetNSが成功しない(2016/12/13)
// 作成当初は「/」が入っていても動作していた筈なのに。。
pathZIP = pathZIP.replace(/\//g,'\\')
// ZIPファイルのFolderObjectを取得する(上記Write直後はファイルが完成していないので、NSが成功するまでループする。)
var zip, カウント=0
while(!(zip=getNS(pathZIP))){
WScript.Sleep(1000)
if(カウント++>5){ WScript.Echo(pathZIP+'\n\n上記PathのnameSpaceが取得できませんでした。\n\n['+zip+']'); WScript.Quit() }
}
// ZIPファイルの中にファイル(フォルダ)をコピー
for(var i=0,L=arg.length;i<L;i++){
var file = getNS(getPFN(arg(i))).ParseName(fs.GetFileName(arg(i)))
// ParseName = shellオブジェクトで扱う「Folder Item Object」を取得するメソッド。
// fs.GetFileで取得できるものとは別物っぽい。
// GetFileで取得したものをCopyHereに渡してもエラーにはならないが、ZIPファイルには何も入らなかった。
// zipファイルにアイテムを追加する前のアイテム数を取得しておく。
var アイテム数 = zip.Items().Count + 1
zip.CopyHere(file)
// ZIPファイル内のアイテム数が増えるまで待機してから次に進まないと、取りこぼしが生じる。(3つのファイルを入れたつもりが2つしか入ってないとか)
while(zip.Items().Count<アイテム数){WScript.Sleep(1)}
// WSFファイルが削除された場合は処理を中断する。作りかけのzipファイルはそのままにしておく。
if(!fs.FileExists(WScript.ScriptFullName)){return}
}
}
リモート判別=function(){
var path0=arrPath[0]
if(path0.match(/^([A-Z]:)\\/)){
// pathがドライブレターから始まっている場合
// ネットワークドライブの割り当てで接続しているドライブかもしれないので確認する。
var ドライブレター=RegExp.$1, drives=WshNetwork.EnumNetworkDrives()
for(var i=0,L=drives.length;i<L;i+=2){
// NetworkDrivesの中に一致するものがあったらリモート処理を開始する。
if(drives.Item(i)==ドライブレター){
Path変換(ドライブレター, drives.Item(i+1))
return リモート処理()
}
}
return zip圧縮()
}
// UNC Pathだけど、実は実行PC自体の共有フォルダ内アイテムかもしれないので、確認する。
path0.match(/^[\\\/]{2}([^\\\/]+)[\\\/]([^\\\/]+)[\\\/]/)
var PC名=RegExp.$1, 共有名=RegExp.$2
// PC名の部分がIPアドレスで記載されている場合は、変数「PC名」の中身をホスト名に変えておく。
if(PC名.match(/^\d+\.\d+\.\d+\.\d+$/)){ PC名 = getホスト名(PC名) }
if(PC名.toLowerCase() == WshNetwork.ComputerName.toLowerCase()){
// ローカルPathに変換してからzip圧縮する
var objEnum=new Enumerator(AXO("WbemScripting.SWbemLocator").ConnectServer().ExecQuery("Select * From Win32_Share")) // where句で絞り込むのはエスケープを考慮するのが面倒なのでやらない。
for(; !objEnum.atEnd() ;objEnum.moveNext()){
if(objEnum.item().name != 共有名){ continue }
if(objEnum.item().name == 共有名){
Path変換(path0.slice(0, ('\\\\'+PC名+'\\'+共有名).length), objEnum.item().path.replace(/\\$/,''))
break
}
}
return zip圧縮()
}
リモート処理()
}
Path変換=function(source, dest){
// argの中身は「N:\folder1\file2.txt」だけど実はネットワークドライブで実体は「\\PC名\共有フォルダ\folder1\file2.txt」だとか
// 逆に中身は「\\PC名\共有フォルダ\folder1\file2.txt」になってるけど実体は「C:\共有フォルダ\folder1\file2.txt」の場合
// arrPathの中身を変換(した方が恐らく読み書きが速いと思うので)する。
var L=source.length
for0L(arrPath, function(i,path){
if(path.indexOf(source)==-1){ return }
arrPath[i] = dest + path.slice(L,path.length)
})
}
リモート処理=function(){
// PsExecが利用可能ならリモートPC側でzip圧縮を実行させる。
arrPath[0].match(/^[\\\/]{2}([^\\\/]+)/)
var PC名=RegExp.$1
var pathTMP, 共有フォルダ=fs.GetParentFolderName(arrPath[0])
while(true){
pathTMP = 共有フォルダ+'/圧縮対象_'+(new Date()).getTime()+'.list'
if(!fs.FileExists(pathTMP)){ break }
}
Write(pathTMP, arrPath.join('\r\n'))
// PsExecのCオプションでスクリプトをリモートPCに複製して実行させられるかと思ったけど、うまくいかなかったので
// 共有フォルダ内にFileCopyで複製してやる。
var pathREM=共有フォルダ+'/remote comp_'+(new Date()).getTime()+'.wsf'
fs.CopyFile(WScript.ScriptFullName, pathREM)
var objRet=PsExec(PC名, 'cscript '+fun(pathREM)+' //job:list '+fun(pathTMP))
if(0 < objRet.StdErr.indexOf('Couldn\'t access ')){
// PsExecによるアクセス権限が無い(or PC名が不正)の場合。
// PsExecが使える場合はリモートPCから削除される予定のファイルが上記理由により削除されずに残ってしまうので、削除する。
fs.DeleteFile(pathTMP)
zip圧縮()
}
if(!fs.FileExists(pathREM)){
WScript.Echo('中断しました。')
WScript.Quit()
}
fs.DeleteFile(pathREM)
}
PsExec=function(PC名,cmd){
// PsExec.exeはUNCパス上にある場合、shell.runから直接呼び出しても動作しない。
// batファイルを経由して呼び出せば動作するので、呼出し用batファイルを作る。
// PsExecで-cオプションを使うと、cmdで指定されたファイルをリモートPCの「%SystemRoot%\system32フォルダ」にコピーして、実行されたのち、終了後に自動削除される。
// …と、http://www.atmarkit.co.jp/ait/articles/1205/11/news147.html ←には記載されているが、以下のようになったので、-cオプションは使わない。cmdに記載する内容はリモートPCの視点で表記すること。
// 事例:クライアントPCのZ:\temp\aaa.wsfをswCopyでリモートPCに複製&実行しようとしたら「Starting Z:\temp\aaa.wsf on リモートPC... PsExec could not start temp/aaa.wsf」というエラーが返された。
var pathBAT=gsf2+'/psexec実行用'+(new Date()).getTime()+'.bat', pathSO=pathBAT+'_StdOut.txt', pathSE=pathBAT+'_StdErr.txt'
Write(pathBAT, [fun(pathPS), '\\\\'+PC名, cmd, '>', fun(pathSO), '2>', fun(pathSE)].join(' '))
shell.Run(fun(pathBAT), 0, true)
var R=function(path){return fs.FileExists(path) ? Read(path) : ''}
var obj = {StdOut:R(pathSO), StdErr:R(pathSE)}
// 後始末
fs.DeleteFile(pathBAT)
fs.DeleteFile(pathSO )
fs.DeleteFile(pathSE )
return obj
}
getホスト名=function(IPアドレス){
/*
[ nbtstat -a 192.168.250.1の出力は下記形式という前提 ]
ローカル エリア接続:
ノード IP アドレス: [192.168.250.1] スコープ ID: []
NetBIOS リモート コンピューター ネーム テーブル
名前 種類 状態
---------------------------------------------
pcName012345678<00> 一意 登録済
ActiveDirectry <00> グループ 登録済
pcName012345678<20> 一意 登録済
ActiveDirectry <1E> グループ 登録済
ActiveDirectry <1D> 一意 登録済
..__MSBROWSE__.<01> グループ 登録済
MAC アドレス = 98-90-96-C7-67-E9
ローカル エリア接続 3:
ノード IP アドレス: [192.168.1.101] スコープ ID: []
ホストが見つかりませんでした。
ワイヤレス ネットワーク接続 3:
ノード IP アドレス: [192.168.123.1] スコープ ID: []
NetBIOS リモート コンピューター ネーム テーブル
名前 種類 状態
---------------------------------------------
pcName012345678<00> 一意 登録済
ActiveDirectry <00> グループ 登録済
pcName012345678<20> 一意 登録済
ActiveDirectry <1E> グループ 登録済
ActiveDirectry <1D> 一意 登録済
..__MSBROWSE__.<01> グループ 登録済
MAC アドレス = 98-90-96-C7-67-E9
[IPアドレスは有効数字のみで表記されている前提]
ためしに「nbtstat -a 192.168.250.001」としてみたが、ホスト名は表示されなかった。
エクスプローラでも「\\192.168.250.001」にはアクセスできなかった。
*/
var pathTMP=gsf2+'/nbtstat_'+(new Date()).getTime()+'.txt', name
shell.run('cmd /C nbtstat -a '+IPアドレス+' > '+fun(pathTMP), 0, true)
name = Read(pathTMP).match(/([^< ]+)<00> 一意/)[1]
fs.DeleteFile(pathTMP)
return name
}
// 以下、基礎関数
AXO = function(name){return new ActiveXObject(name)}
fs = AXO('Scripting.FileSystemObject')
shell = AXO('WScript.Shell')
gsf2 = fs.GetSpecialFolder(2)
Read = function(p){var o=fs.OpenTextFile(p), s=o.AtEndOfStream?'':o.ReadAll(); o.Close(); return s}
Write = function(path,v){with(fs.CreateTextFile(path)){Write(v);Close()}}
RAS = function(p){return Read(p).split('\r\n')}
for0L=function(arr,fun){for(var i=0,L=arr.length,res;i<L;i++){if(res=fun(i,arr[i])){return res}}}
forIn=function(obj,fun){var name,res; for(name in obj){if(res=fun(name, obj[name])){return res}}}
fun = function(path){return path.indexOf(' ')<0 ? path : ('"'+path+'"')}
WshNetwork = AXO("WScript.Network")
getDirList = function(path, swFile){
var pathTMP=gsf2+'/getDirList_'+(new Date()).getTime()+'.txt', arr
shell.Run('cmd /C dir /A'+(swFile?'-':'')+'D /B "'+path+'" > "'+pathTMP+'"' , 0 , true)
arr = fs.FileExists(pathTMP) ? RAS(pathTMP) : []
arr.pop()
try{fs.DeleteFile(pathTMP)}catch(e){}
return arr
}
getFileList=function(path){return getDirList(path, true)}
// メイン処理実行
main()
</script>
</job>
<job id=list>
<script>
// このjobはリモートPC側で起動する場合に呼び出される。
// arg(0)がリスト形式のファイルになっていて、その中身を引数にして自身を再呼出しして、そちらが終了するのを待つ。
// 呼出し先の終了を待たずにこのjobが終了すると、PsExecの機能によってリモートPC上のこのWSFファイルが削除されてしまう。
main=function(){
var arr=[], pathTMP=WScript.Arguments(0)
for0L(RAS(pathTMP), function(i, path){ arr[i] = fun(path) })
AXO('WScript.Shell').run('cscript '+fun(WScript.ScriptFullName)+' '+arr.join(' '), 0, true)
fs.DeleteFile(pathTMP)
}
AXO = function(name){return new ActiveXObject(name)}
fs = AXO('Scripting.FileSystemObject')
Read = function(p){var o=fs.OpenTextFile(p), s=o.AtEndOfStream?'':o.ReadAll(); o.Close(); return s}
RAS = function(p){return Read(p).split('\r\n')}
for0L=function(arr,fun){for(var i=0,L=arr.length,res;i<L;i++){if(res=fun(i,arr[i])){return res}}}
fun = function(path){return path.indexOf(' ')<0 ? path : ('"'+path+'"')}
main()
</script>
</job>
</package>


0 件のコメント:

コメントを投稿