2015/12/10

Windows 2012 WSUSからパッチのステータスを取り出すスクリプト

SUSからパッチのステータス一覧等々必要なデータを取り出すスクリプト。
以前のはWSUS 3.0 SP2では動いていたが、接続時に使う文字列が、WSUS 4.0? (Windows 2012で動くWSUS)では、変更になったのでスクリプトを修正。

Windows Internal Database を使用している場合、WSUS 3.2 の場合、クエリは \\.\pipe\mssql$microsoft##ssee\sql\query、Windows Server 2012 の WSUS の場合、\\.\pipe\Microsoft##WID\tsql\query になる。

  1. # tested with perl2exe-16.00-Win and strawberry-perl-5.16.3.1-32bit
  2. # install / uncompress strawberry perl
  3. # path=%PATH%;;C:\strawberry\perl\bin;C:\strawberry\c\bin
  4. # cpan
  5. # > install Win32::OLE
  6. # > exit
  7. # perl2exe.exe WSUS_report.pl
  8.  
  9. #perl2exe_include "unicore/Heavy.pl";
  10. use overloading; # this is for perl2exe
  11.  
  12. use strict;
  13. use utf8;
  14.  
  15. use POSIX;
  16. #print setlocale(LC_ALL);
  17. setlocale(LC_ALL, 'Japanese_Japan.932');
  18. #print setlocale(LC_ALL);
  19.  
  20.  
  21. sub get_fields($){
  22. my $type = shift @_;
  23. my @out;
  24. @out = split /,/, "updateID,LegacyName,Title,Description,Category,KBArticleID,SecurityBulletinID,MoreInfoURL,RevisionID,SupersedeUpdateCount,SupersededUpdateCount";
  25. return \@out if $type eq "all_patches";
  26.  
  27. @out = split /,/, "EventInstanceID,EventID,TimeAtTarget,TimeAtServer,StateID,SeverityID,MessageTemplate,ReplacementStringsXml,TargetID,ComputerID,LastSyncTime,LastReportedStatusTime,LastReportedRebootTime,IPAddress,FullDomainName,updateID,Title,Category,KBArticleID,SecurityBulletinID";
  28. return \@out if $type eq "error_patches";
  29.  
  30. @out = split /,/, "TargetGroupID,TargetID,IsExplicitMember,TargetID,ComputerID,SID,LastSyncTime,LastReportedStatusTime,LastReportedRebootTime,IPAddress,FullDomainName,IsRegistered,LastInventoryTime,LastNameChangeTime,EffectiveLastDetectionTime,ParentServerTargetID,LastSyncResult,TargetGroupTypeID,Name,Description,TargetGroupID,OrderValue,IsBuiltin,ParentGroupID,GroupPriority";
  31. return \@out if $type eq "host_groups";
  32.  
  33. @out = split /,/, "TargetID,ComputerID,SID,LastSyncTime,LastReportedStatusTime,LastReportedRebootTime,IPAddress,FullDomainName,IsRegistered,LastInventoryTime,LastNameChangeTime,EffectiveLastDetectionTime,ParentServerTargetID,LastSyncResult,TargetID,OSMajorVersion,OSMinorVersion,OSBuildNumber,OSServicePackMajorNumber,OSServicePackMinorNumber,OSLocale,ComputerMake,ComputerModel,BiosVersion,BiosName,BiosReleaseDate,ProcessorArchitecture,LastStatusRollupTime,LastReceivedStatusRollupNumber,LastSentStatusRollupNumber,SamplingValue,CreatedTime,SuiteMask,OldProductType,NewProductType,SystemMetrics,ClientVersion,TargetGroupMembershipChanged,OSFamily,OSDescription";
  34. return \@out if $type eq "hosts";
  35. @out = split /,/, "targetID,FullDomainName,IPAddress,category,Title,SummarizationState,LastReportedStatusTime,SecurityBulletinID,KBArticleID,updateID,RevisionID,SupersedeUpdateCount,SupersededUpdateCount";
  36. return \@out if $type eq "needed_patches";
  37.  
  38. @out = split /,/, "UpdateApprovalId,UpdateID,ComputerTargetGroupId,Title,GroupName,Action,Deadline,CreationDate,AdministratorName,IsOptional,IsStale";
  39.  
  40. return \@out if $type eq "patch_approvals";
  41. return undef;
  42. }
  43.  
  44.  
  45. sub get_sql($){
  46. my $type = shift @_;
  47. return qq{
  48. SELECT
  49. RevisionID,
  50. COUNT(SupersededUpdateID) AS counter
  51. INTO #X1
  52. FROM
  53. SUSDB.dbo.tbRevisionSupersedesUpdate
  54. GROUP BY
  55. RevisionID;
  56.  
  57. SELECT
  58. COUNT(RevisionID) AS counter,
  59. SupersededUpdateID
  60. INTO #Y1
  61. FROM
  62. SUSDB.dbo.tbRevisionSupersedesUpdate
  63. GROUP BY
  64. SupersededUpdateID;
  65.  
  66. SELECT
  67. B.updateID,
  68. B.LegacyName,
  69. A.Title,
  70. A.Description,
  71. D.Title AS Category,
  72. F.KBArticleID,
  73. G.SecurityBulletinID,
  74. H.MoreInfoURL,
  75. A.RevisionID,
  76. #X1.counter AS SupersedeUpdateCount,
  77. #Y1.counter AS SupersededUpdateCount
  78. FROM
  79. SUSDB.dbo.tbPreComputedLocalizedProperty A
  80. INNER JOIN
  81. SUSDB.dbo.tbUpdate B ON A.UpdateID = B.UpdateID
  82. INNER JOIN
  83. SUSDB.dbo.tbRevisionInCategory C ON A.RevisionID = C.RevisionID
  84. INNER JOIN
  85. SUSDB.dbo.tbPrecomputedCategoryLocalizedProperty D ON C.CategoryID = D.CategoryID
  86. INNER JOIN
  87. SUSDB.dbo.tbRevision E on E.localUpdateID = B.localUpdateID
  88. LEFT JOIN
  89. SUSDB.dbo.tbKBArticleForRevision F ON F.RevisionID = E.RevisionID
  90. LEFT JOIN
  91. SUSDB.dbo.tbSecurityBulletinForRevision G ON G.RevisionID = E.RevisionID
  92. INNER JOIN
  93. SUSDB.dbo.tbMoreInfoURLForRevision H ON H.RevisionID = E.RevisionID
  94. LEFT JOIN
  95. #X1 ON A.RevisionID = #X1.RevisionID
  96. LEFT JOIN
  97. #Y1 ON B.updateID = #Y1.SupersededUpdateID
  98. WHERE
  99. A.ShortLanguage = 'en' AND
  100. D.CategoryType = 'UpdateClassification' AND
  101. D.ShortLanguage = 'en' AND
  102. E.islatestRevision = 'True' AND
  103. H.ShortLanguage = 'en'
  104. ORDER BY
  105. G.SecurityBulletinID
  106.  
  107. } if ($type eq 'all_patches');
  108.  
  109. return qq{
  110. SELECT
  111. B.updateID,
  112. B.LegacyName,
  113. A.Title,
  114. A.Description,
  115. D.Title AS Category,
  116. F.KBArticleID,
  117. G.SecurityBulletinID,
  118. H.MoreInfoURL,
  119. A.RevisionID
  120. INTO #X1
  121. FROM
  122. SUSDB.dbo.tbPreComputedLocalizedProperty A
  123. INNER JOIN
  124. SUSDB.dbo.tbUpdate B ON A.UpdateID = B.UpdateID
  125. INNER JOIN
  126. SUSDB.dbo.tbRevisionInCategory C ON A.RevisionID = C.RevisionID
  127. INNER JOIN
  128. SUSDB.dbo.tbPrecomputedCategoryLocalizedProperty D ON C.CategoryID = D.CategoryID
  129. INNER JOIN
  130. SUSDB.dbo.tbRevision E on E.localUpdateID = B.localUpdateID
  131. LEFT JOIN
  132. SUSDB.dbo.tbKBArticleForRevision F ON F.RevisionID = E.RevisionID
  133. LEFT JOIN
  134. SUSDB.dbo.tbSecurityBulletinForRevision G ON G.RevisionID = E.RevisionID
  135. INNER JOIN
  136. SUSDB.dbo.tbMoreInfoURLForRevision H ON H.RevisionID = E.RevisionID
  137. WHERE
  138. A.ShortLanguage = 'en' AND
  139. D.CategoryType = 'UpdateClassification' AND
  140. D.ShortLanguage = 'en' AND
  141. E.islatestRevision = 'True' AND
  142. H.ShortLanguage = 'en'
  143. ORDER BY
  144. G.SecurityBulletinID
  145.  
  146.  
  147. SELECT
  148. distinct B.ComputerID,
  149. D.updateID
  150. INTO #X2
  151. FROM
  152. SUSDB.dbo.tbUpdateStatusPerComputer A
  153. INNER JOIN
  154. SUSDB.dbo.tbComputerTarget B ON A.targetID = B.targetID
  155. INNER JOIN
  156. SUSDB.dbo.tbUpdate D ON A.LocalUpdateID = D.LocalUpdateID
  157. WHERE
  158. A.SummarizationState = 5
  159.  
  160.  
  161. SELECT
  162. E.EventInstanceID,
  163. E.EventID,
  164. E.TimeAtTarget,
  165. E.TimeAtServer,
  166. E.StateID,
  167. E.SeverityID,
  168. E.MessageTemplate,
  169. E.ReplacementStringsXml,
  170. C.TargetID,
  171. C.ComputerID,
  172. C.LastSyncTime,
  173. C.LastReportedStatusTime,
  174. C.LastReportedRebootTime,
  175. C.IPAddress,
  176. C.FullDomainName,
  177. U.updateID,
  178. U.Title,
  179. U.Category,
  180. U.KBArticleID,
  181. U.SecurityBulletinID
  182. FROM
  183. SUSDB.dbo.vwEventHistory E
  184. INNER JOIN
  185. SUSDB.dbo.tbComputerTarget C ON E.ComputerID = C.ComputerID
  186. INNER JOIN
  187. #X1 U ON E.updateID = U.updateID
  188. WHERE
  189. eventID in (161, 163, 182, 185, 186, 192, 195, 198, 364) AND
  190. E.ComputerID IN (SELECT ComputerID FROM #X2) AND
  191. E.updateID IN (SELECT updateID FROM #X2) AND
  192. E.TimeAtServer = (SELECT MAX(TimeAtServer) FROM SUSDB.dbo.vwEventHistory E2
  193. WHERE E.updateID = E2.updateID AND
  194. E.ComputerID = E2.ComputerID)
  195.  
  196. } if ($type eq 'error_patches');
  197.  
  198. return qq{
  199. select A.*, B.*, C.*
  200. from
  201. SUSDB.dbo.tbTargetInTargetGroup as A
  202. INNER JOIN
  203. SUSDB.dbo.tbComputerTarget B ON A.targetID = B.targetID
  204. INNER JOIN
  205. SUSDB.dbo.tbTargetGroup C ON A.TargetGroupID = C.TargetGroupID
  206. ; } if ($type eq 'host_groups');
  207.  
  208. return qq{
  209. SELECT distinct C.*, D.*
  210. FROM
  211. SUSDB.dbo.tbTargetGroup A
  212. LEFT OUTER JOIN
  213. SUSDB.dbo.tbTargetInTargetGroup B ON B.TargetGroupID = A.TargetGroupID
  214. RIGHT OUTER JOIN
  215. SUSDB.dbo.tbComputerTarget C ON B.TargetID = C.TargetID
  216. INNER JOIN
  217. SUSDB.dbo.tbComputerTargetDetail D ON C.TargetID = D.TargetID
  218. } if ($type eq 'hosts');
  219.  
  220. return qq{
  221. SELECT
  222. RevisionID,
  223. COUNT(SupersededUpdateID) AS counter
  224. INTO #X1
  225. FROM
  226. SUSDB.dbo.tbRevisionSupersedesUpdate
  227. GROUP BY
  228. RevisionID;
  229.  
  230. SELECT
  231. COUNT(RevisionID) AS counter,
  232. SupersededUpdateID
  233. INTO #Y1
  234. FROM
  235. SUSDB.dbo.tbRevisionSupersedesUpdate
  236. GROUP BY
  237. SupersededUpdateID;
  238.  
  239. SELECT
  240. distinct B.targetID,
  241. B.FullDomainName,
  242. B.IPAddress,
  243. H.Title AS category,
  244. C.Title,
  245. A.SummarizationState,
  246. B.LastReportedStatusTime,
  247. F.SecurityBulletinID,
  248. I.KBArticleID,
  249. D.updateID,
  250. E.RevisionID,
  251. #X1.counter AS SupersedeUpdateCount,
  252. #Y1.counter AS SupersededUpdateCount
  253. FROM
  254. SUSDB.dbo.tbUpdateStatusPerComputer A
  255. INNER JOIN
  256. SUSDB.dbo.tbComputerTarget B ON A.targetID = B.targetID
  257. INNER JOIN
  258. SUSDB.dbo.tbUpdate D ON A.LocalUpdateID = D.LocalUpdateID
  259. INNER JOIN
  260. SUSDB.dbo.tbPreComputedLocalizedProperty C ON C.updateID = D.updateID
  261. INNER JOIN
  262. SUSDB.dbo.tbRevision E ON D.localUpdateID = E.localUpdateID
  263. LEFT OUTER JOIN
  264. SUSDB.dbo.tbSecurityBulletinForRevision F ON E.RevisionID = F.RevisionID
  265. INNER JOIN
  266. SUSDB.dbo.tbRevisionInCategory G ON E.RevisionID = G.RevisionID
  267. INNER JOIN
  268. SUSDB.dbo.tbPrecomputedCategoryLocalizedProperty H ON G.CategoryID = H.CategoryID
  269. LEFT OUTER JOIN
  270. SUSDB.dbo.tbKBArticleForRevision I ON E.RevisionID = I.RevisionID
  271. LEFT JOIN
  272. #X1 ON E.RevisionID = #X1.RevisionID
  273. LEFT JOIN
  274. #Y1 ON D.updateID = #Y1.SupersededUpdateID
  275. WHERE
  276. (H.ShortLanguage = 'en') AND
  277. (C.ShortLanguage = 'en') AND
  278. (H.CategoryType = 'UpdateClassification') AND
  279. (NOT (A.SummarizationState IN (1, 4)));
  280.  
  281. } if ($type eq 'needed_patches');
  282.  
  283. return qq{
  284. SELECT distinct d.DeploymentGuid AS UpdateApprovalId, u.UpdateID, d.TargetGroupID AS ComputerTargetGroupId,
  285. C.Title, g.Name as GroupName,
  286. (CASE d .ActionID WHEN 0 THEN N'Install' WHEN 1 THEN N'Uninstall' WHEN 2 THEN N'NotApproved' WHEN 3 THEN N'NotApproved' ELSE NULL END)
  287. AS Action, d.Deadline, d.DeploymentTime AS CreationDate, d.AdminName AS AdministratorName, ~ d.IsAssigned AS IsOptional,
  288. ~ r.IsLatestRevision AS IsStale
  289. FROM SUSDB.dbo.tbUpdate AS u
  290. INNER JOIN
  291. SUSDB.dbo.tbRevision AS r ON u.LocalUpdateID = r.LocalUpdateID
  292. INNER JOIN
  293. SUSDB.dbo.tbProperty AS p ON r.RevisionID = p.RevisionID
  294. INNER JOIN
  295. SUSDB.dbo.tbDeployment AS d ON d.RevisionID = r.RevisionID
  296. INNER JOIN
  297. SUSDB.dbo.tbPreComputedLocalizedProperty AS C ON C.updateID = u.updateID
  298. INNER JOIN
  299. SUSDB.dbo.tbTargetGroup AS g ON d.TargetGroupID = g.TargetGroupID
  300. LEFT OUTER JOIN
  301. SUSDB.dbo.tbSecurityBulletinForRevision AS b ON r.RevisionID = b.RevisionID
  302. LEFT OUTER JOIN
  303. SUSDB.dbo.tbKBArticleForRevision AS k ON r.RevisionID = k.RevisionID
  304. INNER JOIN
  305. SUSDB.dbo.tbRevisionInCategory AS rc ON r.RevisionID = rc.RevisionID
  306. INNER JOIN
  307. SUSDB.dbo.tbPrecomputedCategoryLocalizedProperty AS cl ON rc.CategoryID = cl.CategoryID
  308. WHERE
  309. ((p.ExplicitlyDeployable = 1) AND (d.TargetGroupTypeID = 0) AND (d.ActionID IN (0, 1, 3)) OR
  310. (p.ExplicitlyDeployable = 1) AND (d.TargetGroupTypeID = 0) AND (d.ActionID = 2) AND
  311. (d.TargetGroupID <> (select TargetGroupID from SUSDB.dbo.tbTargetGroup where Name = 'All Computers')))
  312. AND (C.ShortLanguage = 'en') AND(cl.ShortLanguage = 'en') AND (cl.CategoryType = 'UpdateClassification')
  313. } if ($type eq 'patch_approvals');
  314.  
  315. return 1;
  316. }
  317.  
  318. sub get_TimeField ($){
  319. my @fields = @{shift @_};
  320. my %output;
  321. foreach my $i (@fields){
  322. if ($i =~ m/time/io || $i =~ m/BiosReleaseDate/o || $i =~ m/CreationDate/o){
  323. $output{$i} = 1
  324. }else{
  325. $output{$i} = 0;
  326. }
  327. }
  328. return \%output;
  329. }
  330.  
  331. sub convert_time ($){
  332. my $init_str = shift;
  333. return "" if ($init_str =~ /^\s*$/);
  334. my @data = split / /, $init_str;
  335. my @date = split /\//, $data[0];
  336. my @time = split /:/, $data[1];
  337. $time[0] = 0 if ($time[0]==12);
  338. $time[0] += 12 if $data[2] eq 'PM';
  339.  
  340. return sprintf("%04d/%02d/%02d %02d:%02d:%02d", $date[2], $date[0], $date[1], @time);
  341. }
  342.  
  343.  
  344. use Win32::OLE;
  345.  
  346. # datetime型のデータが文字列で取得できる。
  347. use Win32::OLE::variant;
  348.  
  349. # 指定タイプライブラリのコンスタントが参照可能
  350. use Win32::OLE::Const 'Microsoft ActiveX Data Objects 2.0 Library';
  351.  
  352. use Win32::OLE::NLS qw(:DEFAULT :LANG :SUBLANG :DATE :TIME);
  353.  
  354. # エラー時に本処理を中止し、Perlがエラーメッセージを出力し、本プロセスが終了する。
  355. Win32::OLE->Option(Warn => 3);
  356.  
  357. use Encode;
  358.  
  359.  
  360. my $usage = qq{WSUS report retrieval tool.
  361.  
  362. Usage: $0 [Options]
  363. Options:
  364. -A Retrieve all supported reports. (Same as "-a -e -g -h -n -p")
  365. -a List of all patches.
  366. -e Error patch list.
  367. -g Hosts list with their group name.
  368. -h host list.
  369. -n List of patches need to be applied.
  370. -p Approval Status report.
  371. -H Show this message.
  372. -P Prefix for output filename.
  373. -3 Use with WSUS ver 3.0 SP2. (default)
  374. -4 Use with Windows 2012 WSUS (ver 4.0?) (takes priority if both of -3 and -4 were set.)
  375. -t timeout (sec) (0 = connection not to timeout. Default is 30 seconds.)
  376.  
  377. The output filename will be set as below.
  378. -a: all_patches_yyyymmdd-HHMM.csv
  379. -e: error_patches_yyyymmdd-HHMM.csv
  380. -g: host_groups_yyyymmdd-HHMM.csv
  381. -h: hosts_yyyymmdd-HHMM.csv
  382. -n: needed_patches_yyyymmdd-HHMM.csv
  383. -p: patch_approvals_yyyymmdd-HHMM.csv
  384.  
  385. };
  386.  
  387. die $usage if ( $#ARGV < 0 );
  388. use Getopt::Std;
  389. use vars qw($opt_A $opt_a $opt_e $opt_g $opt_h $opt_n $opt_p $opt_H $opt_P $opt_3 $opt_4 $opt_t);
  390. getopts('AaeghnpHP:34t:') or die $usage;
  391.  
  392. die $usage if $opt_H;
  393. die $usage if ( $#ARGV != -1 );
  394.  
  395. if ($opt_A){
  396. $opt_a = 1;
  397. $opt_e = 1;
  398. $opt_g = 1;
  399. $opt_h = 1;
  400. $opt_n = 1;
  401. $opt_p = 1;
  402. }
  403.  
  404. $opt_t = 30 if (not defined $opt_t);
  405.  
  406. my %requests;
  407. $requests{all_patches} = 1 if ($opt_a);
  408. $requests{error_patches} = 1 if ($opt_e);
  409. $requests{host_groups} = 1 if ($opt_g);
  410. $requests{hosts} = 1 if ($opt_h);
  411. $requests{needed_patches} = 1 if ($opt_n);
  412. $requests{patch_approvals} = 1 if ($opt_p);
  413.  
  414. my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime();
  415. #秒 分 時 日 月 年 曜日 年初か 夏時間を
  416. # らの 適用して
  417. # 経過日 いるか
  418. $year += 1900; # localtime関数からは1900年から数えた年が返却される。
  419. $mon++; # 月は0から始まるので、表示するときは1を加える。
  420.  
  421. my @day_of_week = qw( ); # 日曜日は0になります。
  422.  
  423. my $date_str=sprintf("%04d%02d%02d-%02d%02d", $year, $mon, $mday, $hour, $min);
  424.  
  425. $opt_P = '' if (! $opt_P);
  426. my %filenames;
  427. $filenames{all_patches} = $opt_P . "all_patches_${date_str}.csv";
  428. $filenames{error_patches} = $opt_P . "error_patches_${date_str}.csv";
  429. $filenames{host_groups} = $opt_P . "host_groups_${date_str}.csv";
  430. $filenames{hosts} = $opt_P . "hosts_${date_str}.csv";
  431. $filenames{needed_patches} = $opt_P . "needed_patches_${date_str}.csv";
  432. $filenames{patch_approvals} = $opt_P . "patch_approvals_${date_str}.csv";
  433.  
  434. # DBサーバー名とDB名設定
  435. my $server;
  436. if ($opt_4) {
  437. $server = 'np:\\\\.\pipe\Microsoft##WID\tsql\query';
  438. }else{
  439. $server = 'np:\\\\.\pipe\mssql$microsoft##ssee\sql\query';
  440. }
  441. my $db = 'SUSDB';
  442.  
  443. # WINDOWS 認証設定
  444. my $connStr = "Provider=sqloledb;".
  445. "Data Source=$server;".
  446. "Initial Catalog=$db;".
  447. "Integrated Security=SSPI;";
  448.  
  449. #print "$connStr\n";
  450.  
  451. # SQL Server 認証設定
  452. #my $id = "testid";
  453. #my $ps = "testps";
  454. #my $connStr = "Provider=sqloledb;".
  455. # "Data Source=$server;".
  456. # "Initial Catalog=$db;".
  457. # "User ID=$id;".
  458. # "Password=$ps;";
  459.  
  460. # DB接続
  461. my $objDB = Win32::OLE->new("ADODB.Connection");
  462. print "Connection test to Windows Internal Database.....\n";
  463. $objDB->Open($connStr);
  464. $objDB->{Errors}->{Count} and die "cannot connect '$connStr'";
  465. print "success!\n";
  466. $objDB->{CommandTimeOut} = $opt_t;
  467.  
  468. # テーブル有無チェック
  469. my $rs = Win32::OLE->new("ADODB.Recordset");
  470. print "Access test to WSUS Database.....\n";
  471. $rs->Open("Select count(*) existance From sysobjects Where NAME = 'tbUpdate'", $objDB);
  472.  
  473. if ($rs->{existance}->{Value} == 0) {
  474. die "Accessing DB is not for WSUS.";
  475. }
  476. print "success!\n";
  477. $rs->Close();
  478. $objDB->Close();
  479.  
  480. foreach my $type (keys %requests){
  481.  
  482. print "Extracting to " . $filenames{$type} . "\n";
  483. print "$type:\t";
  484.  
  485. $objDB->Open($connStr);
  486. $objDB->{Errors}->{Count} and die "cannot connect '$connStr'";
  487. $objDB->{CommandTimeOut} = $opt_t;
  488.  
  489. open OUTFILE, ">:utf8", $filenames{$type} or die "cannot open ".$filenames{$type};
  490.  
  491. # データ読み出し
  492. $rs = Win32::OLE->new("ADODB.Recordset");
  493. $rs->{CursorLocation} = adUseClient;
  494. my $sql_statement = qq{
  495. SET NOCOUNT ON;
  496. SET DATEFORMAT mdy
  497. } . get_sql($type) . ";";
  498.  
  499. #print $sql_statement . "\n";
  500. $rs->Open($sql_statement, $objDB);
  501.  
  502. my @Fields = @{get_fields ($type)};
  503. my %isTime = %{get_TimeField (\@Fields)};
  504.  
  505. print OUTFILE join ',', map {(s/"/""/g or /[\x0d\x0a,]/) ? qq("$_") : $_} @Fields;
  506. print OUTFILE "\x0a";
  507.  
  508. my $counter = 0;
  509. my $counter_length = 0;
  510. $| = 1;
  511.  
  512. if ($type ne 'error_patches'){
  513. while(!$rs->EOF and $rs->{RecordCount} != 0){
  514. $counter++;
  515. if ($counter % 100 == 0){
  516. print "\b" x $counter_length;
  517. $counter_length = length $counter;
  518. print $counter;
  519. }
  520.  
  521. my @items = ();
  522. foreach my $field (@Fields){
  523. my $item =$rs->{Fields}->{$field}->{Value};
  524. $item =~ s/^\s*//gmo;
  525. $item =~ s/\s*$//gmo;
  526. if ($isTime{$field}){
  527. if (ref( $rs->{Fields}->{$field}->{Value}) eq "Win32::OLE::Variant"){
  528. $item = $rs->{Fields}->{$field}->{Value}->Date("yyyy/MM/dd") . " " .
  529. $rs->{Fields}->{$field}->{Value}->Time("hh:mm:ss");
  530. }else{
  531. $item = convert_time ($rs->{Fields}->{$field}->{Value});
  532. }
  533. }
  534. push @items, $item;
  535. }
  536. print OUTFILE join ',', map {(s/"/""/g or /[\x0d\x0a,]/) ? qq("$_") : $_} @items;
  537. print OUTFILE "\x0a";
  538. $rs->MoveNext();
  539. }
  540. }else{
  541. while(!$rs->EOF and $rs->{RecordCount} != 0){
  542. $counter++;
  543. if ($counter % 100 == 0){
  544. print "\b" x $counter_length;
  545. $counter_length = length $counter;
  546. print $counter;
  547. }
  548.  
  549. my @items = ();
  550. my $i = -1;
  551. foreach my $field (@Fields){
  552. $i++;
  553. my $item = $rs->{Fields}->{$field}->{Value};
  554. $item =~ s/^\s*//gmo;
  555. $item =~ s/\s*$//gmo;
  556. if ($i == 7){
  557. my @tmp = split /\n/, $item;
  558. $tmp[2] =~ s/^\s*(.*)<\/string>.*$/\1/go;
  559. $item = $tmp[2];
  560. }
  561. if ($isTime{$field}){
  562. if (ref( $rs->{Fields}->{$field}->{Value}) eq "Win32::OLE::Variant"){
  563. $item = $rs->{Fields}->{$field}->{Value}->Date("yyyy/MM/dd") . " " .
  564. $rs->{Fields}->{$field}->{Value}->Time("hh:mm:ss");
  565. }else{
  566. $item = convert_time ($rs->{Fields}->{$field}->{Value});
  567. }
  568. }
  569. push @items, $item;
  570. }
  571. print OUTFILE join ',', map {(s/"/""/g or /[\x0d\x0a,]/) ? qq("$_") : $_} @items;
  572. print OUTFILE "\x0a";
  573. $rs->MoveNext();
  574. }
  575. }
  576. print "\b" x $counter_length;
  577. print $counter , " records found.\n\n";
  578.  
  579.  
  580. $rs->Close();
  581.  
  582. # DB切断
  583. $objDB->Close();
  584.  
  585. }


ついでに、接続タイムアウトが変更できるようにした。ADOの標準では30秒でタイムアウトするので、巨大なWSUS DBからのデータダウンロードではタイムアウトしてしまうため。



.bat化するなら、こんな感じ。
  1. set output_dir=.\report
  2.  
  3. if not exist %output_dir% (mkdir %output_dir%)
  4. .\WSUS_report.exe -A -P %output_dir%\%computername%_ -4

Perl2ExeがPerl 5.16.3までしかサポートしていない2013年版しか公開されていないのに、Active PerlのCommunity版は5.16.3が公開されていなかったので、strawberry-perlでExe化したせいか、libgcc_s_sjlj-1.dllとlibstdc++-6.dllがないとエラーが出る。C:\strawberry\c\binにある同ファイルを持ってきておけば良いが、新しいPerl2Exeが出たら、Active Perlでコンパイルしなおしてみよう。

WSUS_report_20151209.zip


0 件のコメント:

コメントを投稿