MusicPlayer.psm1

<#
.Synopsis
   Plays music on Windows Media Player in backgroud.
.DESCRIPTION
   Invoke-MusicPlayer Cmdlet automates Windows Media Player to play songs in Background in order (Random/Sequential) chosen by the user.
   Moreover, It generates a balloon notification in bottom Right corner of the screen, whenever a new song starts playing and continues to do that until manually stopped or it completes playing all songs.
.PARAMETER Filter
    String that can be used to filter out songs in a directory. Wildcards are allowed.
.PARAMETER Path
    Path of the Music Directory.
    like, Music -Path C:\music\
.PARAMETER Shuffle
    Switch to play music in shuffle mode, default value is 'sequential'
.PARAMETER Loop
    Switch to continuously play songs in a infinite Loop.
.PARAMETER Stop
Switch to kill any instance of Music playing in backgroud.
.PARAMETER ShowPlaylist

.EXAMPLE
    PS C:\> Music C:\Music\
    
    Directory : C:\Music\
    Count : 4
    PlayDuration(in mins) : 14
    Mode : Sequential
    Current : Cheap Thrills - Vidya Vox Cover.mp3
    Next : Despacito - Luis Fonsi.mp3
   
    Example shows how to run music from by passing a music directory to the Function
.EXAMPLE
    PS C:\> play -Verbose
    VERBOSE: You've not provided a music directory. Looking for cached information from previous execution of this cmdlet
    VERBOSE: Starting a background Job to play audio files


    Directory : C:\Music\
    Count : 4
    PlayDuration(in mins) : 14
    Mode : Sequential
    Current : Cheap Thrills - Vidya Vox Cover.mp3
    Next : Despacito - Luis Fonsi.mp3

    Example shows that in case you don't provide a music directory, the function Looks for the cached information of the diretory from previous us of the function.
    Moreover, It displays the information like Count, Total play duration, and Mode chosen by the user
.EXAMPLE
    PS C:\> play -Shuffle

    Directory : C:\Music\
    Count : 4
    PlayDuration(in mins) : 14
    Mode : Shuffle
    Current : Cheap Thrills - Vidya Vox Cover.mp3
    Next : Whats My Name - Rihanna.mp3

    Choose 'Shuffle' switch to play music in shuffle mode, default value is 'sequential'.
.EXAMPLE
    PS C:\> Music -Shuffle -Loop

    Directory : C:\Music\
    Count : 4
    PlayDuration(in mins) : Infinite
    Mode : Shuffle in Loop
    Current : Lovers On The Sun - David Guetta.mp3
    Next : Cheap Thrills - Vidya Vox Cover.mp3

    Choose 'Loop' switch inorder to continuously play songs in a infinite Loop.
.EXAMPLE
    PS C:\> Music -Stop -Verbose
    VERBOSE: Stoping any Already running instance of Media in background.

    When 'Stop' switch is used any instance Music playing in backgroud stops.
.NOTES
   Author : Prateek Singh
   Twitter : @SinghPrateik
   Blog : RidiCurious.com
#>

Function Invoke-MusicPlayer {
    [cmdletbinding()]
    Param(
        [Alias('P')]  [String] $Path,
        [Alias('F')]  [String] $Filter,
        [Alias('Sh')] [switch] $Shuffle,
        [Alias('St')] [Switch] $Stop,
        [Alias('L')]  [Switch] $Loop,
        [Alias('Pl')] [switch] $ShowPlaylist
    )

    $DefaultPath = "$env:TEMP\MusicPlayer.txt"
    If ($Stop) {
        Write-Verbose "Stoping any Already running instance of Media in background."
        Get-Job MusicPlayer -ErrorAction SilentlyContinue | Remove-Job -Force
    }
    Else {       
        #Caches Path for next time in case you don't enter path to the music directory
        If ($path) {
            $Path | out-file $DefaultPath
        }
        else {
            If ((Get-Content $DefaultPath -ErrorAction SilentlyContinue).Length -ne 0) {
                Write-Verbose "You've not provided a music directory. Looking for cached information from previous execution of this cmdlet"
                $path = Get-Content $DefaultPath

                If (-not (Test-Path $Path)) {
                    Write-Warning "Please provide a path to a music directory.`nFound a cached directory `"$Path`" from previous use, but that too isn't accessible!"
                    # Mark Path as Empty string, If Cached path doesn't exist
                    $Path = ''     
                }
            }
            else {
                Write-Warning "Please provide a path to a music directory."
            }
        }
 
        #initialization Script for back ground job
        $init = {
            # Function to calculate duration of song in Seconds
            Function Get-SongDuration($FullName) {
                $Shell = New-Object -COMObject Shell.Application
                $Folder = $shell.Namespace($(Split-Path $FullName))
                $File = $Folder.ParseName($(Split-Path $FullName -Leaf))
                        
                [int]$h, [int]$m, [int]$s = ($Folder.GetDetailsOf($File, 27)).split(":")
                        
                $h * 60 * 60 + $m * 60 + $s
            }

            # Converts seconds to HH:mm:ss string format
            Function ConvertTo-HHmmss($Seconds) {
                $Time = New-TimeSpan -Seconds $Seconds
                "{0:D2}:{1:D2}:{2:D2}" -f $Time.Hours, $Time.Minutes, $Time.Seconds
            }
                    
            # Function to Notify Information balloon message in system Tray
            Function Show-NotifyBalloon($Message) {
                [system.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null            
                $Global:Balloon = New-Object System.Windows.Forms.NotifyIcon            
                $Balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon((Get-Process -id $pid | Select-Object -ExpandProperty Path))                    
                $Balloon.BalloonTipIcon = 'Info'           
                $Balloon.BalloonTipText = $Message            
                $Balloon.BalloonTipTitle = 'Now Playing'            
                $Balloon.Visible = $true            
                $Balloon.ShowBalloonTip(1000)
            }
                    
            Function PlayMusic($Path, $Shuffle, $Loop, $Filter, $ShowPlaylist) {
                # Calling required assembly
                Add-Type -AssemblyName PresentationCore
    
                # Instantiate Media Player Class
                $MediaPlayer = New-Object System.Windows.Media.Mediaplayer
                        
                # Crunching the numbers and Information
                $FileList = Get-ChildItem $Path -Recurse -Filter $Filter -Include *.mp* | Select-Object fullname, @{n = 'Duration'; e = {get-songduration $_.fullname}}
                $FileCount = ($FileList | Measure-Object).count
                $TotalPlayDuration = [Math]::Round(($FileList.duration | Measure-Object -Sum).sum / 60)
                        
                # Condition to identifed the Mode chosed by the user
                if ($Shuffle) {
                    $Mode = "Shuffle"
                    $FileList = $FileList | Sort-Object {Get-Random}  # Find the target Music Files and sort them Randomly
                }
                Else {
                    $Mode = "Sequential"
                }
                        
                # Check If user chose to play songs in Loop
                If ($Loop) {
                    $Mode = $Mode + " in Loop"
                    $TotalPlayDuration = "Infinite"
                }
                        
                If ($FileList) {
                    If ($FileCount -gt 1) {
                        $Current = Split-Path $FileList[0].fullname -Leaf
                        $Next = Split-Path $FileList[1].fullname -Leaf
                    }
                    ElseIf ($FileCount -eq 1) {
                        $Current = Split-Path $FileList.fullname -Leaf
                        $Next = $null
                        $FileCount = '1'
                    }

                    [PSCustomObject] @{
                        Directory    = $Path
                        Count        = $FileCount
                        'PlayDuration(in mins)' = [String]$TotalPlayDuration
                        Mode         = $Mode
                        Current      = $Current
                        Next         = $Next
                        Playlist     = $FileList | Foreach-Object { [PSCustomObject] @{ FullName = $_.FullName; Duration = $(ConvertTo-HHmmss $_.duration) } }
                    }
                }
                else {
                    Throw "No music files found in directory:`"$path`" that matches Filter: $Filter ." 
                }
                        
                Do {
                    $FileList |ForEach-Object {
                        $CurrentFile = $(Split-Path $_.fullname -Leaf)
                        $Message = "File: {0} `nPlayDuration: {1}`nMode: {2}" -f $CurrentFile, $(ConvertTo-HHmmss (Get-SongDuration $_.fullname)), $Mode            
                        $MediaPlayer.Open($_.FullName)                    # 1. Open Music file with media player
                        $MediaPlayer.Play()                                # 2. Play the Music File
                        Show-NotifyBalloon ($Message)                   # 3. Show a notification balloon in system tray
                        Start-Sleep -Seconds $_.duration                # 4. Pause the script execution until song completes
                        $MediaPlayer.Stop()                             # 5. Stop the Song
                        $Balloon.Dispose(); $Balloon.visible = $false                           
                    }
                }While ($Loop) # Play Infinitely If 'Loop' is chosen by user
            }
        }

        # Removes any already running Job, and start a new job, that looks like changing the track
        If ($(Get-Job Musicplayer -ErrorAction SilentlyContinue)) {
            Get-Job MusicPlayer -ErrorAction SilentlyContinue |Remove-Job -Force
        }

        # Run only if path was Defined or retrieved from cached information
        If ($Path) {
            Write-Verbose "Starting a background Job to play audio files"
            Start-Job -Name MusicPlayer -InitializationScript $init -ScriptBlock {playmusic $args[0] $args[1] $args[2] $args[3] $args[4]} -ArgumentList $path, $Shuffle, $Loop, $Filter, $ShowPlaylist | Out-Null
            Start-Sleep -Seconds 3       # Sleep to allow media player some breathing time to load files
            $Results = Receive-Job -Name MusicPlayer 
            $Results | Select-Object Directory, Count, 'PlayDuration(in mins)', Mode, Current, Next
            If ($ShowPlaylist){
                $Results.Playlist | Format-Table -AutoSize
            }
        }
    }
    
}

Set-Alias -Name Music -Value Invoke-MusicPlayer
Set-Alias -Name Play -Value Invoke-MusicPlayer


# Exporting the members and their aliases
Export-ModuleMember -Function "Invoke-MusicPlayer" -Alias *