cancel
Showing results for 
Search instead for 
Did you mean: 

Backup Image listing via command line

Harpreetchana
Level 1

Hi All,


Seeking your help if anyone have any script for Windows Master Server to get the Image listing follow like
Client name, Primary copy, copy #2.


We are running on Windows Master Server which is connected to Data Domain (as staging disk) and a Tape library.

Our backups are Backups -----> Data Domin (staging disk) -------> Tape (longer retention)
Backup job completed successfully on Disk (Primary Copy)
Backup job duplicated to Tape on Tape (Copy 2)


As of now we are getting this info from Catalog, but very tedious task on daily basis to get this info for 200-250 clients.
Expected output


Client name, Primary copy/Copy #1, copy #2.
Would really appreciate if some can guide me on this request to make this task automate for daily basis.

With Warm Regards
Harpreet Singh

7 REPLIES 7

Nicolai
Moderator
Moderator
Partner    VIP   

A hint:

I think opscenter is the tool you are looking for - is it included in Netbackup. OpsCenter allow you to make customized reports if the built-in doesn't work.

sdo
Moderator
Moderator
Partner    VIP    Certified

First thing to note is that primary copy is not necessarily always copy 1.

And, the first copy number is not always copy number 1.

What I mean is, this is entirely valid:

imageA, copy #9 exists, copy #10 exists, copy #10 is primary

sdo
Moderator
Moderator
Partner    VIP    Certified

Save this as:   list-copies.bat

@echo off
setlocal enabledelayedexpansion

set z_path=%~dp0
set z_name=%~n0

set z_sts=1

set z_file_csv=!z_path!!z_name!.csv
set z_file_ima=!z_path!!z_name!.ima
set z_file_log=!z_path!!z_name!.log
set z_file_sts=!z_path!!z_name!.sts
set z_file_stv=!z_path!!z_name!.stv
set z_file_tmp=!z_path!!z_name!.tmp
set z_file_typ=!z_path!!z_name!.typ
set z_file_vbs=!z_path!!z_name!.vbs

if exist "!z_file_csv!"  del "!z_file_csv!"
if exist "!z_file_ima!"  del "!z_file_ima!"
if exist "!z_file_log!"  del "!z_file_log!"
if exist "!z_file_sts!"  del "!z_file_sts!"
if exist "!z_file_stv!"  del "!z_file_stv!"
if exist "!z_file_tmp!"  del "!z_file_tmp!"
if exist "!z_file_typ!"  del "!z_file_typ!"


call :log ""
call :log "collecting storage server list..."
nbdevquery -liststs -l >"!z_file_sts!"
set /a z_total=0
for /f "tokens=1,2,3" %%a in ('type "!z_file_sts!"') do (
  set z_type=%%c
  if not "!z_type!"=="BasicDisk" (
    find /i "!z_type!" "!z_file_typ!" >nul 2>&1
    if !errorlevel!==1 (
      echo !z_type! >>"!z_file_typ!"
      nbdevquery -listdv -stype !z_type! -l >>"!z_file_stv!"
    )
  )
)
call :log "...done..."


call :log ""
call :log "collecting image list..."
bpimagelist -d 01/01/1970 00:00:00 -l > "!z_file_ima!"
set z_sts=!errorlevel!
if not !z_sts!==0 (
  call :log "...failed to collect image list, status `!z_sts!`, script aborting..."
  goto :end
)
call :log "...done..."


call :log ""
call :log "processing images..."
cscript //nologo "!z_file_vbs!"
set z_sts=!errorlevel!
if not !z_sts!==0 (
  call :log "...failed to process image list, status `!z_sts!`, script aborting..."
  goto :end
)
call :log "...done..."


if not exist "!z_file_csv!" (
  set z_sts=911
  call :log "unable to locate output CSV file, script aborting..."
  goto :end
)


REM put your "blat" command here to email the CSV file.


:end
call :log ""
call :log "Script exiting..."
exit /b !z_sts!


:log
(echo !date! !time:~0,8!  %~1)
(echo !date! !time:~0,8!  %~1)>>"!z_file_log!"
goto :eof

sdo
Moderator
Moderator
Partner    VIP    Certified

Save this as :   list-copies.vbs

Option Explicit

'******************************************************************************************************
'*  File:	list-copies.vbs
'*  Use:	Simple list of images and where the copy numbers are.
'*
'*  Vers	Date		 Who	Description
'*  ----	----		 ---	-----------
'*  v0.01	24-Jan-2019	 sdo	First draft.
'******************************************************************************************************
'* Notes
'* -----
'* 1) Tested on Windows Server 2016, and NetBackup v7.7.3.
'******************************************************************************************************
'* Ideas for future enhancement
'* ----------------------------
'* 1)	If present then this script will read a file named "scriptname.ign", e.g. "list-copies.ign".
'*	This file is a list of elements to ignore from candidate SLP cancellation.  The text in the file
'*	is case insensitive.  Any "<value>" elements from:
'*		<item>:<value>
'*	...will partial match, e.g.:
'*		client:mycli
'*		policy:mypol
'*		slp:myslp
'*	...i.e. there are three items that can be checked for, client, policy, and slp.  This script
'*	searches for "values" in strings, and so will match partial string "mycli" with full client name
'*	of "thisismyclient.mydom", and this render any client containing thr string "mycli" ineligible
'*	for SLP cancellation.  All matches are Case insensitive.
'******************************************************************************************************

Const ci_for_reading = 1
Const ci_for_writing = 2

Dim go_fso
Dim gs_script_spec, gs_script_path, gs_script_name
Dim gd_ignore_policy, gd_ignore_client, gd_ignore_slp

Call s_init()
Call s_main()

WScript.Quit(0)




Sub s_init()
  Dim ls_ignore, ls_recs, ll_rec, ls_rec, ls_items

  Set go_fso = CreateObject( "Scripting.FileSystemObject" )

  gs_script_spec = WScript.ScriptFullName
  gs_script_path = go_fso.GetParentFolderName( gs_script_spec )
  gs_script_name = go_fso.GetBaseName(         gs_script_spec )
  
  Set gd_ignore_client	= CreateObject( "Scripting.Dictionary" )
  Set gd_ignore_policy	= CreateObject( "Scripting.Dictionary" )
  Set gd_ignore_slp	= CreateObject( "Scripting.Dictionary" )

  ls_ignore = gs_script_name & ".ign"
  If go_fso.FileExists( ls_ignore ) Then
    Call s_log( "...loading ignore list..." )
    ls_recs = Split( go_fso.OpenTextFile( ls_ignore, ci_for_reading ).ReadAll, vbcrlf )
    For ll_rec = LBound( ls_recs ) To UBound( ls_recs )
      ls_rec = fs_clean( ls_recs( ll_rec ) )
      ls_rec = Replace( ls_rec, " ", "" )
      ls_rec = LCase( ls_rec )
      Select Case Left( ls_rec, 1 )
      Case "", "#"
      Case Else
        Call s_log( "...ignore: " & ls_rec )
        ls_items = Split( ls_rec, ":" )
        Select Case ls_items( 0 )
        Case "client" : gd_ignore_client.Add ls_items( 1 ), ""
        Case "policy" : gd_ignore_policy.Add ls_items( 1 ), ""
        Case "slp"    : gd_ignore_slp.Add    ls_items( 1 ), ""
        Case Else
          Call s_quit( "...bad keyword in `" & ls_rec & "`..." )
        End Select
      End Select
    Next
    Call s_log( "...done..." )
  Else
    Call s_log( "...no ignore list to load..." )
  End If
End Sub




Sub s_main()
  Dim la_dvs, ll_dv, la_data, ls_dv, ld_dv_name
  Dim la_recs, ll_rec, ll_images, ls_client, ls_image, ls_policy, ls_schedule, ls_ctime, ls_primary, ls_copies, ls_yyyy_mm_dd
  Dim ls_copy, ll_copy, ls_frag, ls_media, lo_chan, ls_csv, ls_image_copy
  Dim ld_image_client, ld_image_primary, ld_image_copies, ld_imagecopy_media, ld_image_policy, ld_image_schedule

  Set ld_dv_name         = CreateObject( "Scripting.Dictionary" )
  Set ld_image_client    = CreateObject( "Scripting.Dictionary" )
  Set ld_image_policy    = CreateObject( "Scripting.Dictionary" )
  Set ld_image_schedule  = CreateObject( "Scripting.Dictionary" )
  Set ld_image_primary   = CreateObject( "Scripting.Dictionary" )
  Set ld_image_copies    = CreateObject( "Scripting.Dictionary" )
  Set ld_imagecopy_media = CreateObject( "Scripting.Dictionary" )

  Call s_log( "" )
  Call s_log( "reading disk volumes..." )
  la_dvs = Split( go_fso.OpenTextFile( gs_script_name & ".stv", ci_for_reading ).ReadAll, vbCrlf )
  For ll_dv = LBound( la_dvs ) To UBound( la_dvs ) - 1
    la_data = Split( la_dvs( ll_dv ), " " )
    ls_dv = la_data( 4 )
    ld_dv_name.Add ls_dv, la_data( 1 )
  Next
  Call s_log( "...done..." )


  Call s_log( "" )
  Call s_log( "reading image list..." )
  la_recs = Split( go_fso.OpenTextFile( gs_script_name & ".ima", ci_for_reading ).ReadAll, vbCrlf )

  For ll_rec = LBound( la_recs ) To UBound( la_recs ) - 1
    la_data = Split( la_recs( ll_rec ), " " )

    Select Case la_data( 0 )
    Case "IMAGE"
      ll_images = ll_images + 1

      ls_client   = la_data(  1 )
      ls_image    = la_data(  5 )
      ls_policy   = la_data(  6 )
      ls_schedule = la_data( 10 )
      ls_ctime    = la_data( 13 )
      ls_copies   = la_data( 20 )
      ls_primary  = la_data( 27 )
      
      ld_image_client.Add   ls_image, ls_client
      ld_image_primary.Add  ls_image, ls_primary
      ld_image_policy.Add   ls_image, ls_policy
      ld_image_schedule.Add ls_image, ls_schedule
      ld_image_copies.Add   ls_image, ls_copies

    Case "HISTO"

    Case "FRAG"
      ls_copy  = la_data( 1 )
      ls_frag  = la_data( 2 )
      ls_media = la_data( 8 )

      If Left( ls_media, 1 ) = "@" Then
        If Not ld_dv_name.Exists( ls_dv ) Then
          Call s_abort( "...unexpected, DV id `" & ls_media & "` for image `" & ls_image & "` not found in DV list..." )
        End If
      End If

      ll_copy = CLng( ls_copy )
      If ( ll_copy < 1 ) Or ( ll_copy > 10 ) Then
        Call s_abort( "...unexpected copy number `" & ls_copy & "` for image `" & ls_image & "`..." )
      End If

      Select Case ls_frag
      Case "-1"
        ld_imagecopy_media.Add ls_image & "|" & ls_copy, ls_media
      End Select

    Case Else
      Call s_quit( "...bad record type `" & la_data( 1 ) & "`, at record `" & ll_rec & "`..." )
    End Select
  Next

  Set la_recs = Nothing

  Call s_log( "...images:  " & ll_images  )
  Call s_log( "...done..." )


  Call s_log( "" )
  Call s_log( "sorting..." )
  Call s_sort( ld_image_client, "key" )
  Call s_log( "...done..." )


  Call s_log( "" )
  Call s_log( "reporting..." )
  Set lo_chan = go_fso.OpenTextFile( gs_script_name & ".csv", ci_for_writing, True )
  lo_chan.WriteLine "Client,Image,Policy,Schedule,Copies,Primary,Copy1,Copy2,Copy3,Copy4,Copy5,Copy6,Copy7,Copy8,Copy9,Copy10"
  For Each ls_image In ld_image_client.Keys
    ls_client   = ld_image_client.Item(   ls_image )
    ls_policy   = ld_image_policy.Item(   ls_image )
    ls_schedule = ld_image_schedule.Item( ls_image )
    ls_primary  = ld_image_primary.Item(  ls_image )
    ls_copies   = ld_image_copies.Item(   ls_image )

    ls_csv = ls_client & "," & ls_image & "," & ls_policy & "," & ls_schedule & "," & ls_copies & "," & ls_primary
    For ll_copy = 1 To 10
      ls_image_copy = ls_image & "|" & ll_copy
      If ld_imagecopy_media.Exists( ls_image_copy ) Then
        ls_csv = ls_csv & "," & ld_dv_name.Item( ld_imagecopy_media.Item( ls_image_copy ) )
      Else
        ls_csv = ls_csv & ","
      End If
    Next
    lo_chan.WriteLine ls_csv
  Next
  lo_chan.Close
  Call s_log( "...done..." )
End Sub



Sub s_log( ps_text )
  Dim ld_now, ls_now
  ld_now = Now()
  ls_now = FormatDateTime( ld_now, vbShortdate ) & " " &   FormatDateTime( ld_now, vbLongtime )
  WScript.Echo ls_now & "  " & ps_text
End Sub

Sub s_sort( pd_tab, ps_what )
  Dim ll_count, ll_i, ll_j, ls_key, ls_item, lb_swap, lb_swapped, ll_compares, ll_swaps
  ll_count = pd_tab.Count
  If ll_count < 2 Then Exit Sub
  Redim ls_keys(  ll_count )
  Redim ls_items( ll_count )
  ll_count = 0
  For Each ls_key In pd_tab.Keys
    ll_count = ll_count + 1
    ls_keys(  ll_count ) = ls_key
    ls_items( ll_count ) = pd_tab.Item( ls_key )
    pd_tab.Remove ls_key
  Next
  If pd_tab.Count <> 0 Then Call s_quit( "...sort not empty after unload..." )
  ll_compares = 0
  ll_swaps    = 0
  For ll_i = ( ll_count - 1 ) To 1 Step -1
    lb_swapped = False
    For ll_j = 1 To ll_i
      lb_swap = False
      Select Case ps_what
      Case "key"
        If ls_keys(  ll_j ) > ls_keys(  ll_j + 1 ) Then lb_swap = True
      Case "item"
        If ls_items( ll_j ) > ls_items( ll_j + 1 ) Then lb_swap = True
      Case Else
        Call s_quit( "...sort, bad p2 `" & ps_what & "`..." )
      End Select
      ll_compares = ll_compares + 1
      If lb_swap Then
        ll_swaps = ll_swaps + 1
        ls_key               = ls_keys(  ll_j )
        ls_keys(  ll_j )     = ls_keys(  ll_j + 1 )
        ls_keys(  ll_j + 1 ) = ls_key
        ls_item              = ls_items( ll_j )
        ls_items( ll_j )     = ls_items( ll_j + 1 )
        ls_items( ll_j + 1 ) = ls_item
        lb_swapped = True
      End If
    Next
    If Not lb_swapped Then Exit For
  Next
  For ll_i = 1 To ll_count
    pd_tab.Add ls_keys( ll_i ), ls_items( ll_i )
  Next
  Call s_log( "...compares:" & ll_compares & ", swaps:" & ll_swaps )
End Sub

Sub s_add( pd_tab, ps_key, px_val )
  Dim lx_val
  If pd_tab.Exists( ps_key ) Then
    lx_val = pd_tab.Item( ps_key ) + px_val
    pd_tab.Remove ps_key
  Else
    lx_val = px_val
  End If
  pd_tab.Add ps_key, lx_val
End Sub

Sub s_quit( ps_text )
  Call s_log( ps_text )
  WScript.Quit( 1 )
End Sub



Function fs_left( ps_text, pl_len )
  fs_left = Left( ps_text & Space( pl_len ), pl_len )
End Function

Function fs_right( ps_text, pl_len )
  fs_right = Right( Space( pl_len ) & ps_text, pl_len )
End Function

Function fs_num( px_num, pl_len )
  Dim ll_num
  ll_num = Int( px_num )
  fs_num = Right( Space( pl_len ) & FormatNumber( ll_num, 0 ), pl_len )
End Function

Function fs_ctime_to_yyyy_mm_dd( ps_ctime )
  Dim ls_res, ld_date
  ld_date = DateAdd( "s", ps_ctime, "01-Jan-1970 00:00:00" )
  ls_res =                         DatePart( "yyyy", ld_date )
  ls_res = ls_res & "-" & fs_zero( DatePart( "m",    ld_date ), 2 )
  ls_res = ls_res & "-" & fs_zero( DatePart( "d",    ld_date ), 2 )
  ls_res = ls_res & " " & WeekDayName( WeekDay( ld_date ), True )
  fs_ctime_to_yyyy_mm_dd = ls_res
End Function

Function fs_zero( ps_num, pl_len )
  fs_zero = Right( String( pl_len, "0" ) & ps_num, pl_len )
End Function

Function fs_clean( ps_rec )
  Dim ls_old, ls_new
  ls_new = ps_rec
  ls_new = Replace( ls_new, vbtab, " " )
  Do
    ls_old = ls_new
    ls_new = Replace( ls_new, "  ", " " )
    ls_new = Trim( ls_new )
  Loop Until ls_new = ls_old
  fs_clean = ls_new
End Function

Function fd_ctime2date( ps_ctime )
  fd_ctime2date = DateAdd( "s", ps_ctime, CDate( "01-Jan-1970 00:00:00" ) )
End Function

sdo
Moderator
Moderator
Partner    VIP    Certified

Save the two scripts in the same folder.  Take the ".txt" file extension off the name.  Run the .bat script.

The scripts assume that the NetBackup binaries are in the system/process wide "path" definition.

sdo
Moderator
Moderator
Partner    VIP    Certified

apologies but this platform will not let me upload a file named "list-copies.bat.txt"

so you'll have to select/copy that from a posting above

watch out for line wrap

sdo
Moderator
Moderator
Partner    VIP    Certified

...an improved version of the main script :

Option Explicit
'******************************************************************************************************
'*  File:	list-copies.vbs
'*  Use:	Simple list of images and where the copy numbers are.
'*
'*  Vers	Date		  Who   Description
'*  ----	----		  ---   -----------
'*  v0.01	24-Jan-2019	  sdo	  First draft.
'*  v0.02   29-Jan-2019   sdo   Handle if there are no disk volumes, i.e. only tape, DSU, DSSU.
'*  v0.03   29-Jan-2019   sdo   Only attempt DV lookup if copy ref begins with "@".
'*  v0.04   29-Jan-2019   sdo   Use fragment '1' as basis for picking up media ID.
'*  v0.05   29-Jan-2019   sdo   If "/" or "\" present in media id then just capture left-most part.
'*  v0.06   29-Jan-2019   sdo   Collect and report media type.
'******************************************************************************************************
'* Notes
'* -----
'* 1) Tested on Windows Server 2016, and NetBackup v7.7.3.
'* 2) Tested on Windows Server 2016, and NetBackup v8.1.1.
'******************************************************************************************************
'* Ideas for future enhancement
'* ----------------------------
'* 1)	If present then this script will read a file named "scriptname.ign", e.g. "list-copies.ign".
'*	This file is a list of elements to ignore from candidate SLP cancellation.  The text in the file
'*	is case insensitive.  Any "<value>" elements from:
'*		<item>:<value>
'*	...will partial match, e.g.:
'*		client:mycli
'*		policy:mypol
'*		slp:myslp
'*	...i.e. there are three items that can be checked for, client, policy, and slp.  This script
'*	searches for "values" in strings, and so will match partial string "mycli" with full client name
'*	of "thisismyclient.mydom", and this render any client containing thr string "mycli" ineligible
'*	for SLP cancellation.  All matches are Case insensitive.
'******************************************************************************************************

Const ci_for_reading = 1
Const ci_for_writing = 2

Const cs_bs = "\"
Const cs_fs = "/"

Dim go_fso
Dim gs_script_spec, gs_script_path, gs_script_name
Dim gd_ignore_policy, gd_ignore_client, gd_ignore_slp

Call s_init()
Call s_main()

WScript.Quit(0)




Sub s_init()
  Dim ls_ignore, ls_recs, ll_rec, ls_rec, ls_items

  Set go_fso = CreateObject( "Scripting.FileSystemObject" )

  gs_script_spec = WScript.ScriptFullName
  gs_script_path = go_fso.GetParentFolderName( gs_script_spec )
  gs_script_name = go_fso.GetBaseName(         gs_script_spec )
  
  Set gd_ignore_client	= CreateObject( "Scripting.Dictionary" )
  Set gd_ignore_policy	= CreateObject( "Scripting.Dictionary" )
  Set gd_ignore_slp	= CreateObject( "Scripting.Dictionary" )

  ls_ignore = gs_script_name & ".ign"
  If go_fso.FileExists( ls_ignore ) Then
    Call s_log( "...loading ignore list..." )
    ls_recs = Split( go_fso.OpenTextFile( ls_ignore, ci_for_reading ).ReadAll, vbcrlf )
    For ll_rec = LBound( ls_recs ) To UBound( ls_recs )
      ls_rec = fs_clean( ls_recs( ll_rec ) )
      ls_rec = Replace( ls_rec, " ", "" )
      ls_rec = LCase( ls_rec )
      Select Case Left( ls_rec, 1 )
      Case "", "#"
      Case Else
        Call s_log( "...ignore: " & ls_rec )
        ls_items = Split( ls_rec, ":" )
        Select Case ls_items( 0 )
        Case "client" : gd_ignore_client.Add ls_items( 1 ), ""
        Case "policy" : gd_ignore_policy.Add ls_items( 1 ), ""
        Case "slp"    : gd_ignore_slp.Add    ls_items( 1 ), ""
        Case Else
          Call s_quit( "...bad keyword in `" & ls_rec & "`..." )
        End Select
      End Select
    Next
    Call s_log( "...done..." )
  Else
    Call s_log( "...no ignore list to load..." )
  End If
End Sub




Sub s_main()
  Dim la_dvs, ll_dv, la_data, ls_dv, ld_dv_name
  Dim la_recs, ll_rec, ll_images, ls_client, ls_image, ls_policy, ls_schedule, ls_ctime, ls_primary, ls_copies, ls_yyyy_mm_dd
  Dim ls_copy, ll_copy, ls_frag, ls_mtype, ls_media, lo_chan, ls_csv, ls_image_copy
  Dim ld_image_client, ld_image_primary, ld_image_copies, ld_imagecopy_media, ld_imagecopy_mtype
  Dim ld_image_policy, ld_image_schedule
  Dim ls_sep, ll_pos1, ll_pos2

  Set ld_dv_name         = CreateObject( "Scripting.Dictionary" )
  Set ld_image_client    = CreateObject( "Scripting.Dictionary" )
  Set ld_image_policy    = CreateObject( "Scripting.Dictionary" )
  Set ld_image_schedule  = CreateObject( "Scripting.Dictionary" )
  Set ld_image_primary   = CreateObject( "Scripting.Dictionary" )
  Set ld_image_copies    = CreateObject( "Scripting.Dictionary" )
  Set ld_imagecopy_mtype = CreateObject( "Scripting.Dictionary" )
  Set ld_imagecopy_media = CreateObject( "Scripting.Dictionary" )

  Call s_log( "" )
  Call s_log( "reading disk volumes..." )
  If go_fso.FileExists( gs_script_name & ".stv" ) Then
    la_dvs = Split( go_fso.OpenTextFile( gs_script_name & ".stv", ci_for_reading ).ReadAll, vbCrlf )
    For ll_dv = LBound( la_dvs ) To UBound( la_dvs ) - 1
      la_data = Split( la_dvs( ll_dv ), " " )
      ls_dv = la_data( 4 )
      ld_dv_name.Add ls_dv, la_data( 1 )
    Next
  End If
  Call s_log( "...done..." )


  Call s_log( "" )
  Call s_log( "reading image list..." )
  la_recs = Split( go_fso.OpenTextFile( gs_script_name & ".ima", ci_for_reading ).ReadAll, vbCrlf )

  For ll_rec = LBound( la_recs ) To UBound( la_recs ) - 1
    la_data = Split( la_recs( ll_rec ), " " )

    Select Case la_data( 0 )
    Case "IMAGE"
      ll_images = ll_images + 1

      ls_client   = la_data(  1 )
      ls_image    = la_data(  5 )
      ls_policy   = la_data(  6 )
      ls_schedule = la_data( 10 )
      ls_ctime    = la_data( 13 )
      ls_copies   = la_data( 20 )
      ls_primary  = la_data( 27 )
      
      ld_image_client.Add   ls_image, ls_client
      ld_image_primary.Add  ls_image, ls_primary
      ld_image_policy.Add   ls_image, ls_policy
      ld_image_schedule.Add ls_image, ls_schedule
      ld_image_copies.Add   ls_image, ls_copies

    Case "HISTO"

    Case "FRAG"
      ls_copy  = la_data( 1 )
      ls_frag  = la_data( 2 )
      ls_mtype = la_data( 5 )
      ls_media = la_data( 8 )

      Select Case ls_frag
      Case "1"
        If Left( ls_media, 1 ) = "@" Then
          If Not ld_dv_name.Exists( ls_dv ) Then
            Call s_abort( "...unexpected, DV id `" & ls_media & "` for image `" & ls_image & "` not found in DV list..." )
          End If
        End If

        ll_copy = CLng( ls_copy )
        If ( ll_copy < 1 ) Or ( ll_copy > 10 ) Then
          Call s_abort( "...unexpected copy number `" & ls_copy & "` for image `" & ls_image & "`..." )
        End If
      
        Select Case ls_mtype
        Case "0"  : ls_mtype = "disk"
        case "1"  : ls_mtype = "unitree"
        Case "2"  : ls_mtype = "tape"
        Case "3"  : ls_mtype = "ndmp"
        Case "5"  : ls_mtype = "checkpoint"
        case "6"  : ls_mtype = "staging"
        Case Else : ls_mtype = ls_mtype & "_unknown"
        End Select

        If Left( ls_media, 1 ) <> "@" Then
          If ls_mtype = "disk" Then
            ls_sep = ""
            If Instr( 1, ls_media, cs_bs ) Then ls_sep = cs_bs	'...probably a Windows path...
            If Instr( 1, ls_media, cs_fs ) Then ls_sep = cs_fs	'...probably a Linux/Unix path...
            If ls_sep <> "" Then
              ll_pos1 = Instr( 1,           ls_media, ls_sep )
              ll_pos2 = Instr( ll_pos1 + 1, ls_media, ls_sep )
              If ll_pos2 > ll_pos1 Then
                ls_media = Mid( ls_media, 1, ll_pos2 )
              Else
                ls_media = Mid( ls_media, 1, ll_pos1 )
              End If
            End If
          End If
        End If
        ld_imagecopy_mtype.Add ls_image & "|" & ls_copy, ls_mtype
        ld_imagecopy_media.Add ls_image & "|" & ls_copy, ls_media
      End Select

    Case Else
      Call s_quit( "...bad record type `" & la_data( 1 ) & "`, at record `" & ll_rec & "`..." )
    End Select
  Next

  Set la_recs = Nothing

  Call s_log( "...images:  " & ll_images  )
  Call s_log( "...done..." )


  Call s_log( "" )
  Call s_log( "sorting..." )
  Call s_sort( ld_image_client, "key" )
  Call s_log( "...done..." )


  Call s_log( "" )
  Call s_log( "reporting..." )
  Set lo_chan = go_fso.OpenTextFile( gs_script_name & ".csv", ci_for_writing, True )
  lo_chan.WriteLine "Client,Image,Policy,Schedule,Copies,Primary,Copy1,Copy2,Copy3,Copy4,Copy5,Copy6,Copy7,Copy8,Copy9,Copy10"
  For Each ls_image In ld_image_client.Keys
    ls_client   = ld_image_client.Item(   ls_image )
    ls_policy   = ld_image_policy.Item(   ls_image )
    ls_schedule = ld_image_schedule.Item( ls_image )
    ls_primary  = ld_image_primary.Item(  ls_image )
    ls_copies   = ld_image_copies.Item(   ls_image )

    ls_csv = ls_client & "," & ls_image & "," & ls_policy & "," & ls_schedule & "," & ls_copies & "," & ls_primary
    For ll_copy = 1 To 10
      ls_image_copy = ls_image & "|" & ll_copy
      If ld_imagecopy_media.Exists( ls_image_copy ) Then
        If Left( ls_image_copy, 1 ) = "@" Then
          ls_csv = ls_csv & "," & ld_imagecopy_mtype.Item( ls_image_copy ) & "-" & ld_dv_name.Item( ld_imagecopy_media.Item( ls_image_copy ) )
        Else
          ls_csv = ls_csv & "," & ld_imagecopy_mtype.Item( ls_image_copy ) & "-" &                  ld_imagecopy_media.Item( ls_image_copy )
        End If
      Else
        ls_csv = ls_csv & ","
      End If
    Next
    lo_chan.WriteLine ls_csv
  Next
  lo_chan.Close
  Call s_log( "...done..." )
End Sub



Sub s_log( ps_text )
  Dim ld_now, ls_now
  ld_now = Now()
  ls_now = FormatDateTime( ld_now, vbShortdate ) & " " &   FormatDateTime( ld_now, vbLongtime )
  WScript.Echo ls_now & "  " & ps_text
End Sub

Sub s_sort( pd_tab, ps_what )
  Dim ll_count, ll_i, ll_j, ls_key, ls_item, lb_swap, lb_swapped, ll_compares, ll_swaps
  ll_count = pd_tab.Count
  If ll_count < 2 Then Exit Sub
  Redim ls_keys(  ll_count )
  Redim ls_items( ll_count )
  ll_count = 0
  For Each ls_key In pd_tab.Keys
    ll_count = ll_count + 1
    ls_keys(  ll_count ) = ls_key
    ls_items( ll_count ) = pd_tab.Item( ls_key )
    pd_tab.Remove ls_key
  Next
  If pd_tab.Count <> 0 Then Call s_quit( "...sort not empty after unload..." )
  ll_compares = 0
  ll_swaps    = 0
  For ll_i = ( ll_count - 1 ) To 1 Step -1
    lb_swapped = False
    For ll_j = 1 To ll_i
      lb_swap = False
      Select Case ps_what
      Case "key"
        If ls_keys(  ll_j ) > ls_keys(  ll_j + 1 ) Then lb_swap = True
      Case "item"
        If ls_items( ll_j ) > ls_items( ll_j + 1 ) Then lb_swap = True
      Case Else
        Call s_quit( "...sort, bad p2 `" & ps_what & "`..." )
      End Select
      ll_compares = ll_compares + 1
      If lb_swap Then
        ll_swaps = ll_swaps + 1
        ls_key               = ls_keys(  ll_j )
        ls_keys(  ll_j )     = ls_keys(  ll_j + 1 )
        ls_keys(  ll_j + 1 ) = ls_key
        ls_item              = ls_items( ll_j )
        ls_items( ll_j )     = ls_items( ll_j + 1 )
        ls_items( ll_j + 1 ) = ls_item
        lb_swapped = True
      End If
    Next
    If Not lb_swapped Then Exit For
  Next
  For ll_i = 1 To ll_count
    pd_tab.Add ls_keys( ll_i ), ls_items( ll_i )
  Next
  Call s_log( "...compares:" & ll_compares & ", swaps:" & ll_swaps )
End Sub

Sub s_quit( ps_text )
  Call s_log( ps_text )
  WScript.Quit( 1 )
End Sub