UpdateCommands.ps1

[CmdletBinding()]
param(
   [Hashtable[]]$Settings = $ExecutionContext.SessionState.Module.PrivateData,
   [String]$BaseDirectory = $ExecutionContext.SessionState.Module.ModuleBase,
   [String]$FileName = $ExecutionContext.SessionState.Module.Name,
   [Switch]$Force
)

# if( $ExecutionContext.SessionState.Module.Name -ne "PowerBot" ) {
# throw "You can only UpdateCommands from within the PowerBot Module ExecutionContext"
# }

$OldSettings = $Settings

function global:New-ProxyFunction {
   param(
      [Parameter(ValueFromPipeline=$True)]
      [ValidateScript({$_ -is [System.Management.Automation.CommandInfo]})]
      $Command
   )
   process {
      $FullName = "{0}\{1}" -f $Command.ModuleName, $Command.Name
      $Pattern  = [regex]::escape($Command.Name)

      [System.Management.Automation.ProxyCommand]::Create($Command) -replace "${Pattern}", "${FullName}"
   }
}

##########################################################################
## Remove all the old hooks
##########################################################################
## Event handlers in powershell have TWO automatic variables: $This and $_
## In the case of SmartIrc4Net:
## $This - usually the connection, and such ...
## $_ - the IrcEventArgs, which just has a Data member
$script:irc = PowerBot\Get-PowerBotIrcClient

Write-Host $($($irc | ft UserName, NickName, Address, IsConnected -auto | Out-String -Stream) -Join "`n") -Fore Green

$NewSettings = Import-LocalizedData -BaseDirectory $BaseDirectory -FileName $FileName
$NewSettings = $NewSettings.PrivateData


Remove-Module PowerBotHooks -ErrorAction SilentlyContinue

$global:PowerBotUserRoles = $NewSettings.RolePermissions.Keys
foreach($Module in @($OldSettings.RolePermissions.Values.Keys) + @($OldSettings.Hooks.Keys) | Select-Object -Unique) {
   Remove-Module $Module -ErrorAction SilentlyContinue
}
foreach($Role in $NewSettings.RolePermissions.Keys) {
   Remove-Module "PowerBot${Role}Commands" -ErrorAction SilentlyContinue
}

## This is the bit where I go all module-crazy on you....
##########################################################################

foreach($Module in @($NewSettings.RolePermissions.Values.Keys) + @($NewSettings.Hooks.Keys) | Select-Object -Unique) {
   Write-Host "Importing" $Module
   try {
      Import-Module $Module -Force:$Force -Global -ErrorAction Stop
   } catch { Write-Warning "Failed to import $Module $_" }
}

## For each role, we generate a new module, and import (nested) the modules and commands assigned to that role
## Then we import that dynamically generated module to the global scope so it can access the PowerBot module if it needs to
foreach($Role in $NewSettings.RolePermissions.Keys) {
   Write-Host "Generating $Role Role Command Module" -Fore Cyan

   New-Module "PowerBot${Role}Commands" {
      param($Role, $RoleModules, $Force)

      foreach($module in $RoleModules.Keys) {
         foreach($command in (Get-Module $module.split("\")[-1]).ExportedCommands.Values | 
            Where { $(foreach($name in $RoleModules.$module) { $_.Name -like $name }) -Contains $True } )
         {
            Set-Content "function:local:$($command.Name)" (New-ProxyFunction $command)
         }  
      }

      # There are a few special commands for Owners and "Everyone" (Users)
      if($Role -eq "Owner") 
      {
         $script:irc = PowerBot\Get-PowerBotIrcClient

         function Quit {
            #.Synopsis
            # Disconnects the bot from IRC
            [CmdletBinding()]
            param(
               # The channel to join
               $message = "As ordered"
            )
            
            $irc.RfcQuit($Message)
            for($i=0;$i -lt 30;$i++) { $irc.Listen($false) }
            $irc.Disconnect()
         }

         function Join {
            #.Synopsis
            # Joins a channel on the server
            [CmdletBinding()]
            param(
               # The channel to join
               $channel
            )
            
            if($channel) {
               $irc.RfcJoin( $channel )
            } else {
               "You have to specify a channel, duh!"
            }
         }

         function Say {
            #.Synopsis
            # Sends a message to the IRC server
            [CmdletBinding()]
            param(
               # Who to send the message to (a channel or nickname)
               [Parameter()]
               [String]$To = $(if($Channel){$Channel}else{$From}),

               # The message to send
               [Parameter(Position=1, ValueFromPipeline=$true)]
               [String]$Message,

               # How to send the message (as a Message or a Notice)
               [ValidateSet("Message","Notice")]
               [String]$Type = "Message"
            )
            foreach($M in $Message.Trim().Split("`n")) {
               $irc.SendMessage($Type, $To, $M.Trim())
            }
         }

         function Update-Command {
           [CmdletBinding()]param([Switch]$Force)
           &(Get-Module PowerBot) { 
             . $PowerBotScriptRoot\UpdateCommands.ps1 -Force:$Force
           }
         }
      } elseif($Role -eq "Guest") {

         function Get-Command {
            #.SYNOPSIS
            # Lists the commands available via the bot
            param(
               # A filter for the command name (allows wildcards)
               [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
               [String[]]$Name = "*"
            )
            process {
               $ExecutionContext.SessionState.Module.ExportedCommands.Values | Where { $_.CommandType -ne "Alias"  -and $_.Name -like $Name } | Sort Name
            }
         }
      } elseif($Role -eq "User") {

         function Get-Alias {
            #.SYNOPSIS
            # Lists the commands available via the bot
            param(
               # A filter for the command name (allows wildcards)
               [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
               [String[]]$Name = "*"
            )
            process {
               Microsoft.PowerShell.Utility\Get-Alias -Definition $ExecutionContext.SessionState.Module.ExportedCommands.Values.Name -ErrorAction SilentlyContinue | Where { $_.Name -like $Name }
            }
         }

         function Get-UserCommand {
            #.SYNOPSIS
            # Lists the commands available via the bot
            param(
               # A filter for the command name (allows wildcards)
               [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
               [String[]]$Name = "*"
            )
            process {
               @(Get-Module PowerBotGuestCommands, PowerBotUserCommands).ExportedCommands.Values | Where { $_.CommandType -ne "Alias"  -and $_.Name -like $Name  -and $_.Name -ne "Get-UserCommand"} | Sort Name
            }
         }
         Set-Alias Get-Command Get-UserCommand
         Export-ModuleMember -Function * -Alias Get-Command
      }
   } -Args ($Role,$NewSettings.RolePermissions.$Role,$Force) | Import-Module -Global -Prefix $(if($Role -notmatch "User|Guest") { $Role } else {""})
}

##########################################################################
## Create a hook module and register all the event handler hooks

Write-Host "Generating PowerBotHooks Module" -Fore Cyan

foreach($EventName in $irc.EventHooks.Keys) {
   foreach($Action in $irc.EventHooks.$EventName) {
      Write-Host "UnHook On$EventName" -Fore Cyan
      try {
         #Requires -version 4.0
         $irc."Remove_On$EventName"( $Action )
      } catch {
         Write-Error "Error unhooking the On$EventName Event"
      }
   }
}

foreach($HookModule in $NewSettings.Hooks.Keys) {
   Write-Host "Importing" $HookModule "for" $ExecutionContext.SessionState.Module.Name
   try {
      foreach($Hook in $NewSettings.Hooks.$HookModule.Keys) {
         $ModuleName = @($HookModule -split "\\")[-1]
         $EventName = $NewSettings.Hooks.$HookModule.$Hook
         if(Microsoft.PowerShell.Core\Get-Command -Name $Hook -ErrorAction SilentlyContinue) {
            $Action = [ScriptBlock]::Create("
               if(`$_.Data) {
                  `$global:Channel = `$_.Data.Channel
                  `$global:Hostname = `$_.Data.Host
                  `$global:Ident = `$_.Data.Ident
                  `$global:Message = `$_.Data.Message
                  `$global:Nick = `$_.Data.Nick
                  `$global:From = `$_.Data.From
               }
 
               $ModuleName\$Hook `$this `$_
 
               Remove-Item Variable:Global:Channel
               Remove-Item Variable:Global:From
               Remove-Item Variable:Global:Hostname
               Remove-Item Variable:Global:Ident
               Remove-Item Variable:Global:Message
               Remove-Item Variable:Global:Nick
               "
)

            try {
               Write-Host "Hook On${EventName} to ${Hook}"
               #Requires -version 4.0
               $irc."Add_On${EventName}"( $Action )
               $irc.EventHooks.$EventName += @( $Action )
            } catch {
               Write-Error "Error hooking the On$EventName Event to $Action"
            }
         } else {
            Write-Host "Could not find the command '$Hook'"
            Write-Host ($MoModule | Format-Table | Out-String)
         }
      }
   } catch {
      Write-Warning "Failed to import $HookModule $_"
   }
}