ebooks.psm1

$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
   Write-Host "Module Library.psm1 removed on $(Get-Date)"
}
function Search-Book {
<#
.SYNOPSIS
A much modified WPF script from 'PowerShell In Action Second Edition' by Bruce Payette. The original example
(Select-String) is described on pages 754-757.
.NOTES
Search-Book will locate any filtered file names containing text entered into the Author textbox and display them
in two columns. The WPF file associated with this script is 'searchbook.xaml'.
Important: to ensure that the Taskbar notifications are displayed for Powershell: right-click the Taskbar then
Properties, Customize; scroll down to Windows Powershell and select Show Icon and Notifications, then OK to quit.
Visit http://www.doublejdesign.co.uk for a selection of alternate Book icons.
This package should contain the following files: books.ico, NewOranges.jpg, searchbook.xaml, ebooks.psm1, ebooks.psd1
.DESCRIPTION
The whole directory tree from that entered in the Path box will be searched for either *.epub, *.mobi, *.azw3, *.azw,
*.apnx, *.pdf or *.txt files. Any of these file names containing the text string from the Author box will be
displayed together with the total number of items. Clicking 'OK' on the display will now close it and instead
open the folder (in Windows Explorer) containing the selected book.
To close the entire filtered list click either the red 'X' at the top or the Cancel button. To close the Control Panel
just click the Exit button,the Navigation Bar icon or use the Esc key.
It will be necessary to wait about 5 seconds before starting Search-Book a second time since the current session will
take this time to end cleanly.
Check 'Get-Help Search-Book -Full' for further information about certain installation details.
.LINK
http://www.SeaStar.co.nf
#>


   [String]$addFolder = 'c:\books'
   $errorActionPreference = 'Stop'
   $script:emsg = ">>>Search-Book closing: input Parameter error." +
                   "`nPlease check all options and restart."
   $script:errorActionPreference = "Stop"

   Add-Type -Assembly PresentationCore,PresentationFramework,System.Drawing,System.Windows.Forms
   trap { 
      $errorPath = Join-Path $PSScriptroot errorlog.txt 
      if ($stream -ne $null) {
         $stream.Close()
      }
      if ($script:notifyIcon -ne $null) {
         $script:notifyIcon.Dispose()
      }
      $show = "[Error] At line:$($_.InvocationInfo.ScriptLineNumber)" +
           " char:$($_.InvocationInfo.OffsetInLine)"
      Get-Date > $errorPath
      $_ >> $errorPath 2>&1                           #Record any error in log file.
      if ($errors -ne $null) {
         $errors.content = "$show (See errorlog.txt)"
      }
      Write-Warning $emsg
      return 
   }

   $mode = [System.Threading.Thread]::CurrentThread.ApartmentState
   if ($mode -ne "STA") {
      $script:emsg = "This script can only be run when PowerShell" +
        "`nis started with the -sta switch."
      Write-Warning "Powershell must be started with the -STA switch"
   }

   function Get-Folder() {
      $folderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog
      $folderBrowser.ShowNewFolderButton = $false
      $folderBrowser.description = "Select new Search Folder:"
      [void]$folderBrowser.ShowDialog()
      return $folderBrowser.SelectedPath                #Return '' for cancel button.
      $folderBrowser.Dispose()
   }

   $counter = 0
   $xamlPath = Join-Path $PSScriptroot searchbook.xaml 
   $stream = [System.IO.StreamReader] $xamlPath
   $form = [System.Windows.Markup.XamlReader]::Load($stream.BaseStream)
   $stream.Close()

   $icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$pshome\powershell.exe")
   $script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon 
   $notifyIcon.Icon = $icon 
   $notifyIcon.BalloonTipIcon  = "Info" 
   $notifyIcon.BalloonTipTitle = "Powershell Search-Book"
   $notifyIcon.BalloonTipText  = "Loading Search-Book GUI. Please wait ..."
   $notifyIcon.Visible = $True 
   $notifyIcon.ShowBalloonTip(50)

   $notifyIcon.add_Click({                         
      $notifyIcon.Visible = $false
      $form.Close()
      dispose 0
   })

   $notifyIcon.add_MouseMove({
      $notifyIcon.Text = "Click to Exit"
   })

   $form.AllowsTransparency = $True
   $form.Opacity = 1.0
   $form.WindowStyle = 'None'

   $errors = $form.FindName("Message")
   $errors.Foreground = "blue"
   $errors.Fontweight = "bold"

   $labelSearch = $form.FindName("Label")
   $filepath = $form.FindName("Path")
   $filepath.Tag = 'Folder'
   $browser = $form.FindName("Browse")
   $browser.Tooltip = "Open Folder Dialog"

   Get-CIMinstance -Namespace root\cimv2 -Class Win32_LogicalDisk |     #Get the default path if external USB attached.
      Select-Object VolumeName, DriveType, DeviceID | 
         Where-Object {($_.VolumeName -ne $null) -and ($_.DeviceID -notmatch "^[c]:$") -and ($_.DriveType -match "^[234]$")} |
            foreach {
               $item = New-Object -TypeName "System.Windows.Controls.ComboBoxItem"
               $counter+= 1
               $item.Content = $_.DeviceID + "\"
               $item.ToolTip = "Volume: " + $_.VolumeName
               $checkPath = $_.DeviceID + "\Books"
               if (Test-Path $checkPath -PathType container) {
                  $item.Content = $_.DeviceID + "\Books"
                  $filepath.SelectedIndex = $counter
               } 
               $filePath.AddChild($item)   #Just add drive letter if no books.
            }

   $box = $form.FindName("FileFilter")
   $item = New-Object -TypeName "System.Windows.Controls.ComboBoxItem"
   $item.Content = "*.all"
   $item.ToolTip = "All book types"             #This is just to add a tooltip.
   $box.AddChild($item)

   if ($filepath.Text -eq "") {               #If nothing here then no display.
      $filepath.Text = $home
   } 

   if (Test-Path $addFolder -PathType container) {
      $filepath.AddChild($addFolder)      #Bypass 'C:\' drive exclusion above.
      $filepath.SelectedItem = $addFolder
   } 

   $TextPattern = $form.FindName("TextPattern")
   $TextPattern.Tag = 'Author'
   $TextPattern.Text = ''
   $TextPattern.Select(0, 0)         
   $TextPattern.Focus() | Out-Null
   $TypeFilter = $form.FindName("FileFilter")
   $TypeFilter.Text = '*.epub'

   $run = $form.FindName("Run")
   $image = $form.FindName("Oranges")                    #We need absolute path here.
   $image.Source = Join-Path $PSScriptroot NewOranges.jpg

   $run.add_Click({
      switch ($filepath,$TextPattern) {
        {$_.Text -eq ''} {
            $errors.content = "No search entry for $($_.Tag). Please try again."
            [Console]::Beep(800,500)
            $_.Focus() | Out-Null
            return 
        }
      }
      if (Test-Path $filepath.Text -PathType container) {      #Now all fields valid.
         $form.DialogResult = $true                 #Triggers the ShowDialog() below.
      } else { 
         [Console]::Beep(800,500)                    #Manually typed entry not valid.
         $errors.content = "Folder '$($Filepath.Text)' does not exist. Please try again."
         $TextPattern.Focus() | Out-Null
      }
   })

   $cancel = $form.FindName("Cancel")
   $cancel.add_Click({
       $notifyIcon.Visible = $false 
       $form.Close()
       dispose 0
   })

   $form.Add_MouseLeftButtonDown({
      $form.DragMove()
   })

   $filepath.Add_MouseEnter({
      $errors.Content = ''
   })

   $browser.add_Click({
       $ignore = 'CurrentCultureIgnoreCase'
       $errors.Content = ''
       $saveFolder = $filePath.Text         
       $filepath.Text = Get-Folder
       if ($filepath.Text -eq '' -or $filepath.Items.Contains($filepath.Text)) {   
          $filepath.Text = $saveFolder         #Ignore Folder 'Cancel' and duplicates.
          $filepath.focus() | Out-Null
          return
       }
       $setMatch = $filepath.Text + "*"       #Disallow 'C:\' or 'C:\Windows' folders.
       if ($env:SystemRoot -ilike $setMatch -or                 
            $filepath.Text.StartsWith($env:SystemRoot,$ignore)) {
           $errors.Content = "Invalid folder: '$($filepath.Text)'"
           $filepath.Text = $saveFolder
           [Console]::Beep(800,500)
       } else {
           $filepath.AddChild($filepath.Text)    #Save for use during current session.
       }
       $filepath.focus() | Out-Null
   })

   $Textpattern.Add_MouseEnter({
       $errors.Content = ''
   })

   $form.Left = 0

   function dispose {
      param([INT]$time = 4)
      Start-Sleep -Seconds $time               #Allow time for message to display.
      $script:notifyIcon.Dispose()
   }

   if ($form.ShowDialog()) {
      $filter = $TypeFilter.Text 
      if ($filter -eq '*.all') {
          $property = @{Label = "Book Title"; Expression = {$_.BaseName}},@{Label = "Type"; Expression = {$_.Extension}},'Directory'
          $books  = (Get-ChildItem $Filepath.Text -Recurse ) |
             Where-Object {($_.extension -ieq ".epub" -or 
                            $_.extension -ieq ".azw3" -or
                            $_.extension -ieq ".azw"  -or
                            $_.extension -ieq ".apnx" -or 
                            $_.extension -ieq ".mobi" -or
                            $_.extension -ieq ".prc") -and ($_.FullName -match $Textpattern.Text)}
      } else {
          $property = @{Label = "Book Title"; Expression = {$_.BaseName}},'Directory'
          $books  = (Get-ChildItem $Filepath.Text -Recurse -Filter $filter) | 
             Where-Object {$_.FullName -match $Textpattern.Text}
      }
      $title = "Search-Book [Using filter $($filter)]"
      $text = $Textpattern.Text.Substring(0,1).ToUpper() + $Textpattern.Text.Substring(1) 
      if ($books.Count -gt 0) {
          $notifyIcon.BalloonTipText = "$($books.count) items found for '$($text)' ($filter)"
          $notifyIcon.Visible = $True 
          $notifyIcon.ShowBalloonTip(300)                    
          $books | Select-Object -Property $property | Out-Gridview -Title $title -OutputMode single -Outvariable temp |
              ForEach-Object {Invoke-Item $_.Directory; dispose 0}             #User clicked 'OK'
          if ($temp -eq $null -or $temp.Count -eq 0) {
             dispose 0                        #User has clicked 'Cancel' or the Grid 'Close' box.
          } 
      } else {
          [Console]::Beep(800,500)
          $notifyIcon.BalloonTipText  = "No matches found for author '$($text)'."
          $notifyIcon.Visible = $True 
          $notifyIcon.ShowBalloonTip(300)
          dispose 
      }
   }
} #End function Search-Book
New-Alias book Search-Book -Description 'Search for any ebooks on the PC'
Export-ModuleMember -Function Search-Book -Alias book