I am writing a powershell script that will start a process, monitor a folder for file changes, and then backup that folder when files are added/changed.
If I run my script from within the Powershell ISE, it works fine, I can monitor the folder and it will properly save a backup as expected.
The problem is that I want to run a batch file that will run the powershell script. But whenever I run the script from a powershell console, or whenver I run my batch file which runs the script, it doesn't work any longer. The script runs and the events are registered. When I copy a file over to the watched folder though, I ONLY get the changed event and not the created event, and the doStuff function is no longer called. I'm not sure how to go about debugging this :/
Following is my script. I have removed parts that don't pertain to what is actualyl wrong currently, so some variables I'm using here you won't see declared but they are there. I get the write-host's to the console when the changed event happens but not the created event (though as stated eariler in the ISE, I get both events and everything works just fine)
#unregister events, in case they weren't unregistered properly before. Just error siliently if they don't exist
Unregister-Event ConsoleStopped -ErrorAction SilentlyContinue
Unregister-Event FileCreated -ErrorAction SilentlyContinue
Unregister-Event FileChanged -ErrorAction SilentlyContinue
Unregister-Event TimerTick -ErrorAction SilentlyContinue
#start the console process
Write-Host Starting console process...
$consoleProcess = Start-Process "$consoleExe" -PassThru
#register to listen for when the console stops
Register-ObjectEvent $consoleProcess Exited -SourceIdentifier ConsoleStopped -Action {
Write-Host Console stopped
#unregister events
Unregister-Event ConsoleStopped -ErrorAction SilentlyContinue
Unregister-Event FileCreated -ErrorAction SilentlyContinue
Unregister-Event FileChanged -ErrorAction SilentlyContinue
if(!$timer.Enabled) {
Unregister-Event TimerElapsed -ErrorAction SilentlyContinue
Remove-Item $timer
}
Remove-Item $fsw
Remove-Item $consoleProcess
}
#watch all files/folders
$filter = '*.*' # You can enter a wildcard filter here.
# In the following line, you can change 'IncludeSubdirectories to $true if required.
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'DirectoryName, FileName, LastWrite'}
#register for FileCreated event
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
write-host Created event has occurred
doStuff($Event)
}
#register for FileChanged event
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action {
Write-Host Change event has occurred
doStuff($Event)
}
function doStuff($event)
{
write-host doStuff has been called
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' in '$folder' was $changeType at $timeStamp" -fore green
if(!$timer.Enabled) {
Write-Host Starting save timer
Register-ObjectEvent $timer Elapsed -SourceIdentifier TimerElapsed -Action $TimerAction
$timer.Start()
Out-File "$backupDir\backup.log" -Append -Force -InputObject "A request for a backup created at $timeStamp"
}
else {
Write-Host A backup has already been request
}
}
function backupSave ()
{
Write-Host Starting backup...
$timestamp = Get-Date -Format o | foreach { $_ -replace ":", "." }
Copy-Item $folder "$backupDir\backup_$timestamp" -Recurse -Force
}
Try moving your functions to the beginning of your script.
i.e. move function doStuff($event) and function backupSave () to the beginning of the script.
The problem may be because when you are calling the functions from the script they haven't been defined yet. It works in Powershell ISE because you ran the script several times and the functions were defined at one time or another.
Related
Working on Windows Server 2019 Datacenter, $PSVersionTable.PSVersion 5.1.17763.2931
In a powershell script started as admin I have to check if an IIS AppPool exists. I used
Get-Item -Path ("IIS:\AppPools\$apppoolname") -ErrorAction SilentlyContinue -ErrorVariable AppPoolError -OutVariable AppPoolOut
The first such Get-Item always fails with "Path IIS does not exist".
Now I set this statemens in front of the script.
$_ = Get-Item -Path ("IIS:\AppPools\DefaultAppPool") -ErrorAction SilentlyContinue -ErrorVariable AppPoolError -OutVariable AppPoolOut
$_ = Get-WebAppPoolState -Name DefaultAppPool -ErrorAction SilentlyContinue -ErrorVariable AppPoolStateError -OutVariable AppPoolStateOut
$_ = Get-Item -Path ("IIS:\AppPools\DefaultAppPool") -ErrorAction SilentlyContinue -ErrorVariable AppPoolError -OutVariable AppPoolOut
Whenever I let run this statement sequence in a newly opened powershell window (as admin) I get the error "Path IIS does not exist" after the first Get-Item. Get-WebAppPoolState returns a proper answer, the second Get-Item doesn't return any data.
Running the same sequence again in the same window, I get meaningful data returned after every request.
Screenshot of 2 successive passes
Can someone explain to me what is going on here and how I can prevent the first Get-Item from failing?
This question already has an answer here:
How to handle failed variable assignments in powershell? [duplicate]
(1 answer)
Closed 2 years ago.
Afternoon!
EDIT
After some more troubleshooting, I've noticed it doesn't seem to want to catch the error from the New-Item -ItemType Directory -Path Z:\PMO\PMOBits\test section. If I put in a Null value, it catches that error in both my Get-Logger function and using Write-output. Why wouldn't it catch the Folder already exists error?
I'm have a logging function in my PowerShell script and I'm trying to incorporate some error handling using this function. Below is the logging function.
Function Get-Logger {
param(
[Parameter(Mandatory=$true)]
[String]$message,
[Parameter(Mandatory=$false)]
[validatescript({[enum]::getvalues([system.consolecolor]) -contains $_})][string]$Color
)
$TimeStamp = Get-Date -Format "MM-dd-yyy hh:mm:ss"
Write-Host $TimeStamp -NoNewline
IF ($Color) {
Write-Host `t $message -ForegroundColor $($color)
} Else {
Write-Host `t $message -ForegroundColor Cyan
}
$logMessage = "[$TimeStamp] $message"
$logMessage | Out-File -Append -LiteralPath $VerboseLogFile
}
I'm testing this with some simple code for the error handling, see the test code below.
Try { New-Item -ItemType Directory -Path Z:\PMO\PMOBits\test }
Catch {
Get-Logger "ERROR: This file already exists"
}
When I run the script, I see the error populate on the terminal that it already exists, but It's not showing in my log file like the catch section should do.
I've also tried to catch the error without using my function below:
try{...}
catch {
Write-Output $_ | Out-File -Append -LiteralPath $VerboseLogFile
}
Add the -ErrorAction Stop with your New-Item statement.
Try { New-Item -ItemType Directory -Path Z:\PMO\PMOBits\test -ErrorAction Stop }
Catch {
Get-Logger "ERROR: This file already exists"
}
My main PowerShell code runs a function that logs to the Windows eventlog. If the level is error it uses a separate event ID which then our monitoring will pick up that exact ID and run an action. However, if I want to specify in the parameter of the main script (not the function) that this time running it use a different Event ID so it will NOT action monitoring, I don't know where to even start on that.
Is there a way to provide a switch parameter in the main script like $NoAlert which then changes the Event ID in the function?
The function of logging lives in a PowerShell module I created. I am importing the module at the beginning of the script and then calling the function during the main script body.
Here is the function:
function WriteLog-SRTProd {
Param(
[string]$logT,
[Parameter(Mandatory=$true)][string]$level,
[String]$LogFileDirT = "\\ServerA\Logs"
)
$RSLogfileT = (Get-ChildItem -Path $LogFileDirT |
sort LastWriteTime |
select -Last 1).Name
## make sure a level is correctly selected (mandatory)
if ("Error","Info","Warn" -NotContains $Level) {
throw "$($Environment) is not a valid name! Please use 'Error', 'Warn', or 'Info'"
}
if ($Level -eq "Info") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) INFO $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Information -EventId 100 -Message $logT -Category 0
}
if ($Level -eq "Warn") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) WARN $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Warning -EventId 200 -Message $logT -Category 0
}
if ($Level -eq "Error") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) ERROR $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Error -EventId 300 -Message $logT -Category 0
}
}
I'd like to run my script like this. When the $NoAlert is passed, it will send that switch to the function. Is this possible? Can I just add the switch in both places and use an if statement in the function for when the NoAlert switch is used?
PS C:\> .\Maintenance.ps1 -NoAlert
Param(
[switch]$NoAlert
)
WriteLog-SRTProd -level Error -logT "Custom Error Message"
I have created own function for logging and stored/installed as module, below is the part of my log module :
you can customize the write statements and add your code for event log. I have added 'NoAction' enum member as per your requirements.
I have used one Enum to separate the log levels
Enum Severity
{
Error = 3
Warning = 4
Informational = 6
Debug = 7
Verbose = 8
NoAction = 0 # AS PER YOUR REQUIREMENTS
}
function Write-Log()
{
[cmdletbinding()]
param
(
[Parameter(Position=0,mandatory=$true)]
[Severity] $LogLevel,
[Parameter(Position=1,mandatory=$true)]
[String] $Message
)
$TimeStamp = "$(Get-Date -format HH:mm:ss)" ;
Switch($LogLevel)
{
([Severity]::Error.ToString())
{
Write-Error "`t$TimeStamp : $Message`n" -ErrorAction Stop
break;
}
([Severity]::Warning.ToString())
{
Write-Warning "`t$TimeStamp : $Message`n" -WarningAction Continue
break;
}
([Severity]::Informational.ToString())
{
Write-Information "INROMATION:`t$TimeStamp : $Message`n" -InformationAction Continue
break;
}
([Severity]::Verbose.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
([Severity]::NoAction.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
} # END OF SWITCH
} # END OF FUNCTION
Sample Call :
Write-Log -LogLevel ([Severity]::Informational) -Message "test log message using info level"
Output :
INROMATION: 09:40:15 : test log message using info level
I have decided to just add a new parameter to both function and main script named $NoAlert. I have added an If($NoAlert){WriteLog-SRPProd -NoAlert} to the main script (messy, but its what I needed done). then in the Function, If($NoAlert){EventID 111}. so basically I am using the switch in the main script that then calls the NoAlert switch in the function. This is all done with a few added If/Else statements.
Hopefully that makes sense. Like I said its not the best answer, but I wanted to get it done and still provide an answer here in this post.
Here is my script which returns a boolean
param($fileName, $path, $contextMenuItem, $automationDLLPath)
function CloseWindowsExplorer()
{
(New-Object -comObject Shell.Application).Windows() | foreach-object {$_.quit()}
}
Try
{
Import-Module $automationDLLPath
# Open the explorer window in a maximized form
Start-Process explorer $path -WindowStyle Maximized
Start-Sleep 1
Get-UIAActiveWindow
# Get the "Items View" in Explorer to go through all the lements
$list = Get-UIAList -Name 'Items View' -TimeOut 30000;
# Get the file specified in the feature file from the Items View
# Added a sleep because the VM takes time to perform the functions
Start-Sleep 1
$file = $list | Get-UIAListItem -Name $fileName;
# Perform a single click on the file to invoke a right click on it
Invoke-UIAListItemSelectItem -InputObject $file -ItemName $fileName;
# Added a sleep because the VM takes time to perform the functions
Start-Sleep 1
# Invoke the right click on the selected file
$menu = Invoke-UIAControlContextMenu -InputObject $file;
Start-Sleep 1
# select our context menu item
$menuItem = Get-UIAMenuItem -InputObject $menu $contextMenuItem -TimeOut 30000;
# Display error if the required item in the context menu is not found
if( $null -eq $menuItem){
%{ Write-Host 'cannot find menuItem' }
}
# Invoke the item if found in the context menu
else{
Invoke-UIAMenuItemClick -InputObject $menuItem
}
# close the windows explorer and return true
CloseWindowsExplorer
Write-Output "true"
}
Catch
{
# close the explorer window as a part of teardown and return false to reflect test failure
Write-Output "false"
CloseWindowsExplorer
}
I want the script to print the exact exception that was caught as well as return a boolean but in this case it is just returning false when the script fails. Any help is appreciated
Basically I need to print the exception as if the try catch block does not exist.
You need to use the special variable $_
This small example shows how it works:
try {
testmenow
} catch {
Write-Host $_
}
$_ is an object so you can do
$_|gm
in the catch block in order to see the methods you can call.
My script keeps bugging me with the following exception
copy-item : Cannot find drive. A drive with the name 'F' does not exist.
At C:\Program Files (x86)\CA\ARCserve Backup\Templates\RB_Pre_Process.ps1:58 char:1
+ copy-item -Path $drive -Destination $DST_DRIVE -Recurse -ErrorAction Stop
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (F:String) [Copy-Item], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.CopyItemCommand
This is what my script looks like. I am mounting an ISO image on drive F: and I have added a "start-slepp -s 5" command so i can verify the image get's mounted, which it does!
$BACKUP_PATH = "E:\00_BACKUP_DATA"
$DR_PATH = "E:\01_DR_DATA"
$ISO_IMAGE = "C:\Program Files (x86)\CA\ARCserve Backup\Templates\Winpe_x64.iso"
$DST_DRIVE = "E:\"
try {
New-EventLog -LogName Application -Source "RB_Pre_Process.ps1" -ErrorAction Stop
} catch [System.InvalidOperationException] {
Write-host $_
}
try {
Write-Host "Preparing RDX cartridge..."
# Query for disk object
$disk_number = (Get-Disk | Where-Object -Property FriendlyName -like "TANDBERG RDX*").Number
# Remove partitions
Get-Disk $disk_number | Clear-Disk -RemoveData -Confirm:$false | Out-Null
# Create new partition
New-Partition -DiskNumber $disk_number -UseMaximumSize | Out-Null
# Format partition
Format-Volume -DriveLetter E -FileSystem NTFS -NewFileSystemLabel "RDX_TAPE" -Confirm:$false | Out-Null
# Set partition as active
Set-Partition -DriveLetter E -IsActive:$true | Out-Null
} catch {
Write-Host $_
Write-EventLog -LogName Application -Source $MyInvocation.MyCommand.Name -EventID 2 -Message $_
}
try {
Write-Host "Creating folder structure..."
new-item -itemtype directory -Path $BACKUP_PATH -ErrorAction stop | Out-Null
new-item -itemtype directory -path $DR_PATH -ErrorAction stop | Out-Null
} catch {
Write-Host $_
Write-EventLog -LogName Application -Source $MyInvocation.MyCommand.Name -EventID 2 -Message $_
}
try {
Write-Host "Mounting ISO image..."
$image = Mount-DiskImage -ImagePath $ISO_IMAGE -PassThru -ErrorAction Stop
} catch [ParameterBindingException] {
Write-Host $_
Write-EventLog -LogName Application -Source $MyInvocation.MyCommand.Name -EventId 2 -Message $_
}
$drive = ($image | Get-Volume).DriveLetter
$drive += ":\*"
Start-Sleep -s 5
try {
Write-Host "Copying ISO content..."
copy-item -Path $drive -Destination $DST_DRIVE -Recurse -ErrorAction Stop
} catch {
Write-Host $_
Write-EventLog -LogName Application -Source $MyInvocation.MyCommand.Name -EventId 2 -Message $_
}
try {
Write-Host "Unmounting ISO image..."
Dismount-DiskImage -ImagePath $ISO_IMAGE -ErrorAction Stop
} catch [System.Exception] {
Write-Host $_
Write-EventLog -LogName Application -Source $MyInvocation.MyCommand.Name -EventId 2 -Message $_
}
So, what's going wrong here? Sometimes it works sometimes not...
I "solved" the issue... my script is working perfectly fine when it's getting started directly from the PowerShell prompt instead of the PowerShell ISE... So the IDE is the culprit.
it seems the mounted image can't be reached in powershell. I think it's a limitation of the provider. A possible workaround is issuing CMD command. You could replace
copy-item -Path $drive -Destination $DST_DRIVE -Recurse -ErrorAction Stop
with
& cmd /C "copy F:\* G:\dest\"
Here I just give an example, you may need to do further work to copy recursively..you could use the xcopy or robocopy which could handle recursive copy.