use File::Find; use File::Stat ":FIELDS"; # all exports use File::Basename; use Data::Dumper; use Time::HiRes qw(gettimeofday tv_interval); use Pod::Usage; use Getopt::Long; # Test for Win32 operating system -------------------------------------------------------------------- my $windows=($^O=~/Win/)?1:0;# Are we running on windows? if ($windows){ use Win32::File; use Win32::FileOp qw(Mapped); use Win32::FileSecurity qw(Get EnumerateRights); use Win32::OLE qw(in with); use Win32::OLE::Variant; use Win32::OLE::Const 'Microsoft Excel'; } # use strict; use constant True => 1; my(@image_info,$usage,$i,$excel); my ($name) = Win32::LoginName; my ($runtime) = time; my ($datetime) = &datetime($runtime); my ($machine) = Win32::NodeName; # Determine Machine (Computer) name my ( $ID, $MAJOR, $MINOR, $YEAR, $MONTH, $DAY, $TIME, $AUTHOR ) = q$Id: fsinv.pl 1.15 2016/03/11 08:36:32 Dave.Roberts Exp $ =~ /Id: (.+)\s+(\d+)\.(\d+)\s(\d+)\/(\d+)\/(\d+)\s(\d\d:\d\d:\d\d)\s(.+)\s(.+)/; my (@CallArgs) = @ARGV; # Process calling arguments ------------------------------------------------------------------- my $images =''; # True to report image files, default false my $help =''; # True to display help message, default false my $man =''; # True to display help message, default false my $version = ""; # True to display version, default false my $mp3 = ""; # True to report additional information from audio (mp3) files my $report = ""; GetOptions ("images" => \$images, "help" => \$help, "man" => \$man, "version" => \$version, "report=s" => \$report, "mp3" => \$mp3) # flag or &pod_help("Error in command line arguments\n"); # images switch is true --------------------------------------------------------------------------- if ($images) { use Image::ExifTool qw(:Public); @image_info = qw(Model CreateDate CreationDate MediaCreateDate DateTimeOriginal ImageHeight ImageWidth XResolution YResolution ResolutionUnit GPSLatitude GPSLongitude ); } # --------------------------------------------------------------------------------------------------- if ($help){ &pod_help(); } # --------------------------------------------------------------------------------------------------- if ($version){ my $message_text = "$ID version $MAJOR.$MINOR"; print "$message_text\n"; exit; } # --------------------------------------------------------------------------------------------------- if ($man){ pod2usage(-exitstatus => 0, -verbose => 2); exit; } # --------------------------------------------------------------------------------------------------- if ($report){ unless ($report =~ /(excel|dump)/ ){ my $message_text = "valid values for report are excel or dump"; print "$message_text\n"; exit; } } #----------------------------------------------------------------------------------------------------- if ($mp3){ use MP3::Info; } BEGIN { $| = 1; } # autoflush screen output... my $hmessage = " "; my $fs = shift or &pod_help("The filesystem name should be specified when calling fsinv\n"); my ($message) = <<"EOT"; ******************************************************************************* Executing script $ID Script Details: Version $MAJOR.$MINOR Date $DAY-$MONTH-$YEAR Time $TIME Author $AUTHOR Execution: Computer $machine Account $name Date/Time $datetime Calling Arguments: @CallArgs ******************************************************************************* EOT print $message; die if ($usage); my $start = gettimeofday; my($inf); my($loExcel); # determine a Windows style drive mapping if (($windows)&($fs =~ /^([a-zA-Z]:)/)){ $inf->{drive} = $1; $inf->{$inf->{drive}} = Mapped $inf->{drive}; } print " * Reading file system... "; &File::Find::find(\&wanted,$fs); my $end = gettimeofday; my $time = $end - $start; printf STDERR "\b complete (took %s)\n",&best_int($time); # Test if Excel is available for output -------------------------------------------- if ($windows){ # Use existing instance if Excel is already running or start Excel eval {$loExcel = Win32::OLE->GetActiveObject('Excel.Application')}; print "Excel not installed\n" if $@; unless (defined $loExcel) { $loExcel = Win32::OLE->new('Excel.Application','Quit') or print "Unable to start Excel\n"; } print "Excel is installed\n" if ($loExcel); } # Logic for determining report to produce ------------------------------------- if ($report eq ""){ if ($loExcel){ $report = "excel" ; }else{ $report = "dump" ; } } if ($report eq "excel"){ print "Generating Excel report\n"; &excelreport($loExcel,$inf); }elsif($report eq "dump"){ print "Generating dump report\n"; print Dumper($inf); }else{ print "No report defined\n"; } exit; #------------------------------------------------------------------------------- sub wanted { use Integer; if (-d $_){ my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($_); $inf->{d}->{$File::Find::name . "/"}->{atime} = &datetime($atime); $inf->{d}->{$File::Find::name . "/"}->{mtime} = &datetime($mtime); $inf->{d}->{$File::Find::name . "/"}->{ctime} = &datetime($ctime); $inf->{stats}->{totalfolders} +=1; }elsif(-f $_){ # we're checking files my($name,$path,$suffix) = fileparse($File::Find::name, qr/\.[^.]*/); my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($_); $inf->{f}->{$path . $name . $suffix}->{path}=$path; $inf->{f}->{$path . $name . $suffix}->{suffix}=$suffix; $inf->{f}->{$path . $name . $suffix}->{filename}=$name . $suffix; $inf->{f}->{$path . $name . $suffix}->{size} = $size; $inf->{f}->{$path . $name . $suffix}->{atime} = &datetime($atime); $inf->{f}->{$path . $name . $suffix}->{mtime} = &datetime($mtime); $inf->{f}->{$path . $name . $suffix}->{ctime} = &datetime($ctime); $inf->{d}->{$path}->{size} +=$size; $inf->{d}->{$path}->{sizeinc} +=$size; $inf->{d}->{$path}->{totalfiles} +=1; $inf->{stats}->{totalfiles} +=1; if (($images) & ($suffix =~ /jpg$/i)){ # get image info - for image files my $tool = Image::ExifTool->new(); $tool->Options(Unknown => 1, DateFormat => '%d/%M/%Y %H:%M:%S'); if ($tool->ExtractInfo($_)){ my($z); foreach $z (@image_info){ if ($tool->GetValue($z)){ $inf->{f}->{$path . $name . $suffix}->{image}->{$z} = $tool->GetValue($z); } } }else{ print "Image::ExifTool Error: $!\n"; } }elsif(($mp3) & ($suffix =~ /mp3$/i)){ # get image info - for image files print "getting mp3 info for $path $name $suffix\n"; my $info = get_mp3info($_); my $tags = get_mp3tag($_) or print "No TAG info\n"; $inf->{f}->{$path . $name . $suffix}->{MP3INFO}=$info; $inf->{f}->{$path . $name . $suffix}->{MP3TAGS}=$tags; } my($parent) = $path; while ($parent =~ s/[^\/]+\/$//){ last unless ($inf->{d}->{$parent}); $inf->{d}->{$parent}->{sizeinc} +=$size; } } &spin(); # prints next character } #------------------------------------------------------------------------------- sub datetime { my($t) = @_; my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($t); my($month) = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')[$mon]; #my($datetime) = sprintf "%2d %s %02d", my($datetime) = sprintf "%2d %s %4d %2d:%02d:%02d", $mday,$month,($year+1900), $hour, $min, $sec; return $datetime; } #------------------------------------------------------------------------------- sub excelreport { my($excel) = shift; my($inf) = shift; my($vnt); $excel->{Visible} = 1; $excel->{SheetsInNewWorkbook} = 3; my ($book) = $excel->Workbooks->Add; # Create Files Worksheet ................................................................................................. my ($w) = $book->Worksheets(2); my($r) = 1; my ($col) = 7; $w->Cells($r,1)->Font() -> {'Bold'}= True; $w->Cells($r,1)->{Value} = "Name"; $w->Cells($r,2)->Font() -> {'Bold'}= True; $w->Cells($r,2)->{Value} = "File"; $w->Cells($r,3)->Font() -> {'Bold'}= True; $w->Cells($r,3)->{Value} = "Suffix"; $w->Cells($r,4)->Font() -> {'Bold'}= True; $w->Cells($r,4)->{Value} = "Size"; $w->Cells($r,5)->Font() -> {'Bold'}= True; $w->Cells($r,5)->{Value} = "atime (accessed)"; $w->Cells($r,6)->Font() -> {'Bold'}= True; $w->Cells($r,6)->{Value} = "mtime (modified)"; $w->Cells($r,7)->Font() -> {'Bold'}= True; $w->Cells($r,7)->{Value} = "ctime (created)"; with($w->Columns(1), ColumnWidth => 40, HorizontalAlignment => xlLeft); with($w->Columns(2), ColumnWidth => 25, HorizontalAlignment => xlLeft); with($w->Columns(4), NumberFormat => '#,##0', ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(5), NumberFormat => 'dd/mm/yyyy hh:mm:ss', ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(6), NumberFormat => 'dd/mm/yyyy hh:mm:ss', ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(7), NumberFormat => 'dd/mm/yyyy hh:mm:ss', ColumnWidth => 18, HorizontalAlignment => xlRight); my %cols; if ($images) { # Create titles/formats for columns showing image info $col = 8; foreach (@image_info) { $cols{$_} = $col; $w->Cells($r,$col)->Font() -> {'Bold'}= True; $w->Cells($r,$col)->{Value} = $_; with($w->Columns($col), NumberFormat => 'dd/mm/yyyy hh:mm:ss', ColumnWidth => 18, HorizontalAlignment => xlRight) if ($_ =~ /Date/); $col++; } } $w->{Name} = "Files"; foreach (sort keys %{$inf->{f}}){ $r++; $w->Cells($r,1)->{Value} = &backslash($_); $w->Cells($r,2)->{Value} = $inf->{f}->{$_}->{filename}; $w->Cells($r,3)->{Value} = $inf->{f}->{$_}->{suffix}; $w->Cells($r,4)->{Value} = Win32::OLE::Variant->new(VT_R8, $inf->{f}->{$_}->{size}); $w->Cells($r,5)->{Value} = Win32::OLE::Variant->new(VT_DATE, $inf->{f}->{$_}->{atime}); $w->Cells($r,6)->{Value} = Win32::OLE::Variant->new(VT_DATE, $inf->{f}->{$_}->{mtime}); $w->Cells($r,7)->{Value} = Win32::OLE::Variant->new(VT_DATE, $inf->{f}->{$_}->{ctime}); if ($images) { $col = 8; foreach my $z (@image_info) { if ($inf->{f}->{$_}->{image}) { if ($z =~ /Date/){ $w->Cells($r,$col)->{Value} = Win32::OLE::Variant->new(VT_DATE, $inf->{f}->{$_}->{image}->{$z}); }else{ $w->Cells($r,$col)->{Value} = $inf->{f}->{$_}->{image}->{$z}; } } $col++; } } } $w->Range("A:" . &col($col))->Columns->AutoFilter("1","<>"); # set AutoFilter on $w->Range("A:" . &col($col))->Columns->AutoFit; # Set AutoFit Column Widths for (1..$col) { #foreach( qw (A B C D E F G)){ # Set max column width to 50 characters if (($w->Columns(&col($_))->{'ColumnWidth'}) > 50 ) { $w->Columns(&col($_))->{'ColumnWidth'} = 50; } } # Create Folders Worksheet ............................................................................................... $w = $book->Worksheets(3); $r = 1; $w->Cells($r,1)->Font() -> {'Bold'}= True; $w->Cells($r,1)->{Value} = "Directory"; $w->Cells($r,2)->Font() -> {'Bold'}= True; $w->Cells($r,2)->{Value} = "Size (excl subfolders)"; $w->Cells($r,3)->Font() -> {'Bold'}= True; $w->Cells($r,3)->{Value} = "Size (incl subfolders)"; $w->Cells($r,4)->Font() -> {'Bold'}= True; $w->Cells($r,4)->{Value} = "Total Files"; $w->Cells($r,5)->Font() -> {'Bold'}= True; $w->Cells($r,5)->{Value} = "atime (accessed)"; $w->Cells($r,6)->Font() -> {'Bold'}= True; $w->Cells($r,6)->{Value} = "mtime (modified)"; $w->Cells($r,7)->Font() -> {'Bold'}= True; $w->Cells($r,7)->{Value} = "ctime (created)"; with($w->Columns(1), ColumnWidth => 40, HorizontalAlignment => xlLeft); with($w->Columns(2), ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(3), ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(4), ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(2), NumberFormat => '#,##0', ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(3), NumberFormat => '#,##0', ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(5), NumberFormat => 'dd/mm/yyyy hh:mm:ss', ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(6), NumberFormat => 'dd/mm/yyyy hh:mm:ss', ColumnWidth => 18, HorizontalAlignment => xlRight); with($w->Columns(7), NumberFormat => 'dd/mm/yyyy hh:mm:ss', ColumnWidth => 18, HorizontalAlignment => xlRight); $w->{Name} = "Folders"; foreach (sort keys %{$inf->{d}}){ $r++; $w->Cells($r,1)->{Value} = &backslash($_); $w->Cells($r,2)->{Value} = Win32::OLE::Variant->new(VT_R8, $inf->{d}->{$_}->{size}); $w->Cells($r,3)->{Value} = Win32::OLE::Variant->new(VT_R8, $inf->{d}->{$_}->{sizeinc}); $w->Cells($r,4)->{Value} = $inf->{d}->{$_}->{totalfiles}; $w->Cells($r,5)->{Value} = Win32::OLE::Variant->new(VT_DATE, $inf->{d}->{$_}->{atime}); $w->Cells($r,6)->{Value} = Win32::OLE::Variant->new(VT_DATE, $inf->{d}->{$_}->{mtime}); $w->Cells($r,7)->{Value} = Win32::OLE::Variant->new(VT_DATE, $inf->{d}->{$_}->{ctime}); } $w->Range("A:F")->Columns->AutoFilter("1","<>"); # set AutoFilter on $w->Range("A:F")->Columns->AutoFit; # Set AutoFit Column Widths foreach( qw (A B C D E F)){ # Set max column width to 50 characters if (($w->Columns($_)->{'ColumnWidth'}) > 50 ) { $w->Columns($_)->{'ColumnWidth'} = 50; } } # Create Report Summary Worksheet ................................................................................................. $w = $book->Worksheets(1); my($c) = 2; $w->{Name} = "Report Summary"; $w->Cells(2,$c)->Font() -> {'Bold'}= True; $w->Cells(2,$c)->{Value} = "Account Name"; $w->Cells(3,$c)->Font() -> {'Bold'}= True; $w->Cells(3,$c)->{Value} = "Computer Name"; $w->Cells(4,$c)->Font() -> {'Bold'}= True; $w->Cells(4,$c)->{Value} = "Date/Time - Report Start"; $w->Cells(4,$c)->Font() -> {'Bold'}= True; $w->Cells(5,$c)->{Value} = "Runtime"; $w->Cells(5,$c)->Font() -> {'Bold'}= True; $w->Cells(6,$c)->{Value} = "Name"; $w->Cells(6,$c)->Font() -> {'Bold'}= True; $w->Cells(7,$c)->{Value} = "Version"; $w->Cells(7,$c)->Font() -> {'Bold'}= True; $w->Cells(8,$c)->{Value} = "Date"; $w->Cells(8,$c)->Font() -> {'Bold'}= True; $w->Cells(9,$c)->{Value} = "Argument"; $w->Cells(9,$c)->Font() -> {'Bold'}= True; $w->Cells(10,$c)->{Value} = "Total Files"; $w->Cells(10,$c)->Font() -> {'Bold'}= True; $w->Cells(11,$c)->{Value} = "Total File Size"; $w->Cells(11,$c)->Font() -> {'Bold'}= True; $w->Cells(12,$c)->{Value} = "Total Folders"; $w->Cells(12,$c)->Font() -> {'Bold'}= True; if ($inf->{drive} & $inf->{$inf->{drive}}){ $w->Cells(13,$c)->{Value} = "Drive Mapping"; $w->Cells(13,$c)->Font() -> {'Bold'}= True; } with($w->Columns(1), ColumnWidth => 2, HorizontalAlignment => xlLeft); with($w->Columns(2), ColumnWidth => 25, HorizontalAlignment => xlLeft); with($w->Columns(3), ColumnWidth => 20, HorizontalAlignment => xlLeft); $c = 3; with($w->Cells(4,$c), NumberFormat => 'dd/mm/yyyy hh:mm:ss'); with($w->Cells(5,$c), NumberFormat => 'dd/mm/yyyy'); with($w->Cells(10,$c), NumberFormat => '#,##0', HorizontalAlignment => xlRight); with($w->Cells(11,$c), NumberFormat => '[>999999999999]0.000,,,," TB";[>999999999]0.000,,," GB";0.000,," MB"', HorizontalAlignment => xlRight); with($w->Cells(12,$c), NumberFormat => '#,##0', HorizontalAlignment => xlRight); $w->Cells(2,$c)->{Value} = $name; $w->Cells(3,$c)->{Value} = $machine; $w->Cells(4,$c)->{Value} = Win32::OLE::Variant->new(VT_DATE, $datetime); $w->Cells(5,$c)->{Value} = &best_int($time); $w->Cells(6,$c)->{Value} = $ID; $w->Cells(7,$c)->{Value} = sprintf "%d.%d",$MAJOR,$MINOR; $w->Cells(8,$c)->{Value} = sprintf "%d-%s-%d",$DAY,$MONTH,$YEAR; my $arg; foreach (@CallArgs){ $arg .= $_ . " "; } $w->Cells(9,$c)->{Value} = $arg; $w->Cells(10,$c)->{Value} = $inf->{stats}->{totalfiles}; $w->Cells(11,$c)->{Value} = "=SUM(FILES!D:D)"; $w->Cells(12,$c)->{Value} = $inf->{stats}->{totalfolders}; if ($inf->{drive} & $inf->{$inf->{drive}}){ $w->Cells(13,$c)->{Value} = sprintf "%s => %s",$inf->{drive},$inf->{$inf->{drive}}; } } #------------------------------------------------------------------------------- sub backslash { my ($a) = @_; $a =~ s/\//\\/g; return $a; } #------------------------------------------------------------------------------- sub best_int { use integer; my $s = shift; return sprintf ":%02d sec", $s if $s < 60; my $m = $s / 60; $s = $s % 60; return sprintf "%02d:%02d min:sec", $m, $s if $m < 60; my $h = $m / 60; $m %= 60; return sprintf "%02d:%02d:%02d hr:min:sec", $h, $m, $s if $h < 24; my $d = $h / 24; $h %= 24; return sprintf "%d:%02d:%02d:%02d day:hr:min:sec", $d, $h, $m, $s; } #------------------------------------------------------------------------------- sub spin { my %spinner = ( '|' => '/', '/' => '-', '-' => '\\', '\\' => '|' ); $i = (! defined $i) ? '|' : $spinner{$i}; print STDERR "\b$i"; } sub col{ my ($num) = @_; my($base)=26; my($mem) = ""; my($h,$c); while ($num >= $base){ $h = int($num/$base); $c = $num -($h*$base); if ($c eq 0){ $c = $base; $h--; } $mem = chr($c+64) . $mem; $num = $h; } $mem = chr($num+64) . $mem unless ($num eq 0); return $mem; } #------------------------------------------------------------------------------- sub pod_help{ my $message_text = shift; my $exit_status = 2; ## The exit status to use my $verbose_level = 1; ## The verbose level to use my $filehandle = \*STDERR; ## The filehandle to write to pod2usage( { -message => $message_text , -exitval => $exit_status , -verbose => $verbose_level, -output => $filehandle } ); exit; } #------------------------------------------------------------------------------- __END__ =pod =head1 NAME fsinv.pl - A simple program that documents a file system =head1 SYNOPSIS fsinv.pl filesystem [options] =head1 OPTIONS =over 8 =item B<--help> Show brief usage help information. =item B<--manual> Read the manual, with examples. =item B<--images> Include information from image files in the report =item B<--version> Show the version number and exit. =item B<--images> Read and report the following attributes of .jpg suffix files (see MP3::Info for more information). These are only reported if they are identified. =over 16 =item Model =item CreateDate =item CreationDate =item MediaCreateDate =item DateTimeOriginal =item ImageHeight =item ImageWidth =item XResolution =item YResolution =item ResolutionUnit =item GPSLatitude =item GPSLongitude =back =item B<--mp3> Read MP3TAGS and MP3INFO for MP3 files and include these in the report where files have the .mp3 suffix (see MP3::Info for more information) =item B<--report=name> Where report is set to either "excel" - the default for a windows system with Microsoft Excel installed "dump" - the default where Excel is not available =back =head1 EXAMPLES fsinv.pl //server/share/folder fsinv.pl \\server\share\folder -images fsinv.pl D:\temp fsinv.pl /myfolder =head1 DESCRIPTION This is a simple program to generate an inventory report for a file system. The report is generated as an Excel file (on Win32 systems with Excel installed) , or otherwise is dumped as STDOUT. =head1 COPYRIGHT AND LICENSE Copyright 2015 Dave Roberts This is released under the Artistic License. See L. =head1 AUTHOR Dave Roberts -- david.roberts@cpan.org =head1 VERSION $Id: fsinv.pl 1.15 2016/03/11 08:36:32 Dave.Roberts Exp $ =cut