I have a script that will install VLC from the server. What happens when I run the script is every computer name in my text file will start the install process where you will then hit "next next next" (basically).
I was curious of how I can write these scripts for any software to just install and not prompt for all of those "next" and just install on its own. Is this possible to do?
# Run As Administrator
Function Get-FileName{
[CmdletBinding()]
Param(
[String]$Filter = "|*.*",
[String]$InitialDirectory = "C:\")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $InitialDirectory
$OpenFileDialog.filter = $Filter
[void]$OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
$file = Get-FileName -InitialDirectory $env:USERPROFILE\Desktop -Filter "Text files (*.txt)|*.txt|All files (*.*)|*.*"
ForEach ($item in (Get-Content $file)) {
$sitem = $item.Split("|")
$computer = $sitem[0].Trim()
$user = $sitem[1].Trim()
$filepath = Test-Path -Path "\\$computer\c$\Program Files (x86)\VideoLAN\VLC\"
If ($filepath -eq $false) {
Get-Service remoteregistry -ComputerName $computer | Start-Service
Copy-Item -Path "\\server\Unsupported Software\VLC MediaPlayer" -Destination "\\$computer\c$\windows\temp\" -Container -Recurse -Force
$InstallString = '"C:\windows\temp\VLC MediaPlayer\vlc-2.2.1-win32.exe"'
([WMICLASS]"\\$computer\ROOT\CIMV2:Win32_Process").Create($InstallString)
"$computer" + "-" + "$(Get-Date)" | Out-File -FilePath "\\server\Unsupported Software\VLC MediaPlayer\RemoteInstallfile.txt" -Append
} Else {
"$computer" + "_Already_Had_Software_" + "$(Get-Date)" | Out-File -FilePath "\\server\Unsupported Software\VLC MediaPlayer\RemoteInstallfile.txt" -Append
}
}
According to the documentation, you can just use the command line switch to install VLC like:
vlc-2.0.1-win32.exe /L=1033 /S
Where /S probably is a switch for silent installation.
Related
I want to change the permission of a folder on multiple pcs, provided in a csv file. the csv doesn't have a header, just the computernames.
problem is, that it does not import the pc names. i can't use a txt file
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$file = import-csv -path "$dir\pc.csv"
foreach($pc in $file) {
try {
$acl = get-acl -path "\\$pc\c$\Program Files (x86)\testfolder"
$new = "users","full","ContainerInherit,ObjectInherit","None","Allow"
$accessRule = new-object System.Security.AccessControl.FileSystemAccessRule $new
$acl.SetAccessRule($accessRule)
$acl | Set-Acl "\\$pc\c$\Program Files (x86)\testfolder"
Write-Output $([string](get-date) + "`t $pc success") | out-file -append -filepath "$dir\acl_success.log"
}
catch {Write-Output $([string](get-date) + "`t $pc failed") | out-file -append -filepath "$dir\acl_failed.log"
}
}
Is it possible to use a invoke-command setting the folder acl using the provided csv file?
You say "i can't use a txt file", but what you describe IS a txt file. No header, just pc names each on its own line.
Also, your script would greatly benefit if you would indent the code.
Try
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
# set ErrorAction to 'Stop' in order to catch errors
$oldErrorAction = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
Get-Content -Path "$dir\pc.csv" | ForEach-Object {
$pc = $_ # capture this for when we hot the catch block
$path = "\\$pc\c$\Program Files (x86)\testfolder"
try {
$acl = Get-Acl -LiteralPath $path
$accessrule = [System.Security.AccessControl.FileSystemAccessRule]::new('Users', 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
$acl.SetAccessRule($accessRule)
$acl | Set-Acl -LiteralPath $path
# write to both the console and to file
("{0}`t{1} success" -f (Get-Date).ToString(), $pc) | Add-Content -LiteralPath "$dir\acl_success.log" -PassThru
}
catch {
("{0}`t{1} failed" -f (Get-Date).ToString(), $pc) | Add-Content -LiteralPath "$dir\acl_success.log" -PassThru
}
}
# restore previous ErrorAction
$ErrorActionPreference = $oldErrorAction
Using Invoke-Command:
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$script = {
# set ErrorAction to 'Stop' in order to catch errors
$oldErrorAction = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
# you're now running this on the remote pc, so use local path
$path = "C:\Program Files (x86)\testfolder"
try {
$acl = Get-Acl -LiteralPath $path
$accessrule = [System.Security.AccessControl.FileSystemAccessRule]::new('Users', 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
$acl.SetAccessRule($accessRule)
$acl | Set-Acl -LiteralPath $path
# output the message
"{0}`t{1} success" -f (Get-Date).ToString(), $env:COMPUTERNAME
}
catch {
"{0}`t{1} failed" -f (Get-Date).ToString(), $env:COMPUTERNAME
}
# restore previous ErrorAction
$ErrorActionPreference = $oldErrorAction
}
$allPCs = #(Get-Content -Path "$dir\pc.csv") # force it to be an array, evenif just one pc name in the file
# run the scriptblock on the $allPCs array
$result = Invoke-Command -ComputerName $allPCs -ScriptBlock $script
$result | Where-Object { $_ -match 'success' } | Add-Content -Path "$dir\acl_success.log" -PassThru
$result | Where-Object { $_ -match 'failed' } | Add-Content -Path "$dir\acl_failed.log" -PassThru
Instead of this:
$file = import-csv -path "$dir\pc.csv"
Use:
$file = get-content "$dir\pc.csv"
I currently have a powershell script, which print out some information regarding the files which passed in as argument..
The command for executing the script, it done as such:
.\myscript.ps1 -accessitem C:\folder
I want to apply the script on all files and folder on the drive C:, is it possible i for loop to list all files, and pass the path as argument for the script?
The script:
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True,Position=0)]
[String]$AccessItem
)
$ErrorActionPreference = "SilentlyContinue"
If ($Error) {
$Error.Clear()
}
$RepPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$RepPath = $RepPath.Trim()
$str = $AccessItem -replace ':',''
$str = $AccessItem -replace '/','.'
$FinalReport = "$RepPath\"+$str+".csv"
$ReportFile1 = "$RepPath\NTFSPermission_Report.txt"
If (!(Test-Path $AccessItem)) {
Write-Host
Write-Host "`t Item $AccessItem Not Found." -ForegroundColor "Yellow"
Write-Host
}
Else {
If (Test-Path $FinalReport) {
Remove-Item $FinalReport
}
If (Test-Path $ReportFile1) {
Remove-Item $ReportFile1
}
Write-Host
Write-Host "`t Working. Please wait ... " -ForegroundColor "Yellow"
Write-Host
## -- Create The Report File
$ObjFSO = New-Object -ComObject Scripting.FileSystemObject
$ObjFile = $ObjFSO.CreateTextFile($ReportFile1, $True)
$ObjFile.Write("NTFS Permission Set On -- $AccessItem `r`n")
$ObjFile.Close()
$ObjFile = $ObjFSO.CreateTextFile($FinalReport, $True)
$ObjFile.Close()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ObjFSO) | Out-Null
Remove-Variable ObjFile
Remove-Variable ObjFSO
If((Get-Item $AccessItem).PSIsContainer -EQ $True) {
$Result = "ItemType -- Folder"
}
Else {
$Result = "ItemType -- File"
}
$DT = Get-Date -Format F
Add-Content $ReportFile1 -Value ("Report Created As On $DT")
Add-Content $ReportFile1 "=================================================================="
$Owner = (Get-Item -LiteralPath $AccessItem).GetAccessControl() | Select Owner
$Owner = $($Owner.Owner)
$Result = "$Result `t Owner -- $Owner"
Add-Content $ReportFile1 "$Result `n"
(Get-Item -LiteralPath $AccessItem).GetAccessControl() | Select * -Expand Access | Select IdentityReference, FileSystemRights, AccessControlType, IsInherited, InheritanceFlags, PropagationFlags | Export-CSV -Path "$RepPath\NTFSPermission_Report2.csv" -NoTypeInformation
Add-Content $FinalReport -Value (Get-Content $ReportFile1)
Add-Content $FinalReport -Value (Get-Content "$RepPath\NTFSPermission_Report2.csv")
Remove-Item $ReportFile1
Remove-Item "$RepPath\NTFSPermission_Report2.csv"
Invoke-Item $FinalReport
}
If ($Error) {
$Error.Clear()
}
I would prefer a outside command doing this, as workings of the script should not be altered, it it used for single file testing..
There are two ways to do this:
Add -Recurse Flag to the script
Run the script on each directory
I'm going with option two since the script looks complicated enough that I don't want to touch it.
$path_to_script = "C:\path\to\myscript.ps1"
$start_directory = "C:\folder"
# Call Script on Parent Directory
& "$path_to_script" -AccessItem "$start_directory"
# Call Script on any Child Directories within the "$start_directory"
foreach($child in (ls "$start_directory" -Recurse -Directory))
{
$path = $child.FullName
& "$path_to_script" -AccessItem "$path"
}
Basically, I'm calling the script on the parent directory and any sub-directories within the parent directory.
I am trying to figure out how to write a powershell script that will automatically install office2010 on multiple pcs. I am struggling on the portion where you create the text file that we loop through listing the ComputerName and the Users Login. I have researched this all over the web but for some reason am unable to get this to work.
Function Get-FileName{
[CmdletBinding()]
Param(
[String]$Filter = "|*.*",
[String]$InitialDirectory = "C:\")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $InitialDirectory
$OpenFileDialog.filter = $Filter
[void]$OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
ForEach ($computer in (GC (Get-FileName -InitialDirectory $env:USERPROFILE\Desktop -Filter "Text files (*.txt)|*.txt|All files (*.*)|*.*"))) {
$filepath = Test-Path -Path "\\$computer\C:\Program Files (x86)\Microsoft Office"
If ($filepath -eq $false)
{
Get-Service remoteregistry -ComputerName $computer | Start-Service
Copy-Item -Path "\\server\Orig\Install\Office2010" -Destination "\\$computer\c$\windows\temp\" -Container -Recurse -Force
# $InstallString = '"C:\windows\temp\Office 2010\setup.exe"'
# ([WMICLASS]"\\$computer\ROOT\CIMV2:Win32_Process").Create($InstallString)
# "$computer" + "-" + "(Get-Date)" | Out-File -FilePath "\\server\Orig\Install\RemoteInstallfile.txt" -Append
# }
# Else
# {
# "$computer" + "_Already_Had_Software_" + "(Get-Date)" | Out-File -FilePath "\\server\Orig\Install\RemoteInstallfile.txt" -Append
}
}
ComputerList.txt
IT-Tech | David
IT-Tech would be the computer name and David would be the user. Then I would have a list like this line by line in the txt file.
So i was thinking I could do something like this Listing the computer name and then the user name of how to install. This part confuses me though just trying to learn and see what this powershell stuff is all about!
Any help with this would be greatly appreciated!
A line of your file, as you've said, will contain something like "IT-Tech | David", so when you iterate through that file that's the value of $computer. You then attempt to use this as the computer name call which will of course fail because first you need to split it out.
I will also point out it is extremely bad form to abbreviate and use aliases in scripts, you should only use them in the console. Also for readability it helps to split complex bits out.
$file = Get-FileName -InitialDirectory $env:USERPROFILE\Desktop -Filter "Text files (*.txt)|*.txt|All files (*.*)|*.*"
ForEach ($item in (Get-Content $file)) {
$sitem = $item.Split("|")
$computer = $sitem[0].Trim()
$user = $sitem[1].Trim()
$filepath = Test-Path -Path "\\$computer\C:\Program Files (x86)\Microsoft Office"
If ($filepath -eq $false)
{
Get-Service remoteregistry -ComputerName $computer | Start-Service
Copy-Item -Path "\\server\Orig\Install\Office2010" -Destination "\\$computer\c$\windows\temp\" -Container -Recurse -Force
<#
$InstallString = '"C:\windows\temp\Office 2010\setup.exe"'
([WMICLASS]"\\$computer\ROOT\CIMV2:Win32_Process").Create($InstallString)
"$computer" + "-" + "(Get-Date)" | Out-File -FilePath "\\server\Orig\Install\RemoteInstallfile.txt" -Append
}
Else
{
"$computer" + "_Already_Had_Software_" + "(Get-Date)" | Out-File -FilePath "\\server\Orig\Install\RemoteInstallfile.txt" -Append
#>
}
}
Note that this will NOT install the product if the installer is already in the destination, not sure if that is intended behaviour or not.
I have a script i scraped from other scripts. It works except i am quite new to powershell and am a sys admin not a dev, (but reading my ass off). I can get the scrtipt to work downloading attachments from inbox in outlook but need it to download attachments from a subfolder instead:
############################# Outlook Call ##############################
$olFolderInbox = 6
$outlook = new-object -com outlook.application;
$ns = $outlook.GetNameSpace("MAPI");
$inbox = $ns.GetDefaultFolder($olFolderInbox)
$messages = $inbox.items
write-host $messages.count
$messcount = $messages.count
foreach($message in $messages){
##############Save Attachments################
$filepath = "c:\attachments\"
$message.attachments|foreach {
Write-Host $_.filename
$attr = $_.filename
$_.saveasfile((Join-Path $filepath $_.filename))
$a = $_.filename
If ($a.Contains("")) {
$_.saveasfile((Join-Path $filepath $a))
}
}
}
###########END##########
Any Ideas anyone? Would be massively grateful.
$OutputFolder = 'C:\tests';
$ErrorActionPreference= 'silentlycontinue'
$outlook = New-Object -ComObject Outlook.Application;
$olFolderInbox = 6;
$ns = $outlook.GetNameSpace("MAPI");
$inbox = $ns.GetDefaultFolder($olFolderInbox);
$inbox.Folders `
| ? Name -eq 'colour' `
| % Items `
| % Attachments `
| % {
$OutputFileName = Join-Path -Path $OutputFolder -ChildPath $_.FileName;
if (Test-Path $OutputFileName) {
$FileDirectoryName = [System.IO.Path]::GetDirectoryName($OutputFileName);
$FileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($OutputFileName);
$FileExtension = [System.IO.Path]::GetExtension($OutputFileName);
for ($i = 2; Test-Path $OutputFileName; $i++) {
$OutputFileName = "{0} ({1}){2}" -f (Join-Path -Path $FileDirectoryName -ChildPath $FileNameWithoutExtension), $i, $FileExtension;
}
}
Write-Host $OutputFileName;
$_.SaveAsFile($OutputFileName)
}
Remove-Item -Path C:\tests\*.jpg
Remove-Item -Path C:\tests\*.png
Start-Process –FilePath “c:\tests\*.docx” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.xlsx” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.doc” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.xls” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.pptx” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.ppt” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.xls” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.msg” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.xlsm” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.rtf” –Verb Print -PassThru
Start-Process –FilePath “c:\tests\*.pdf” –Verb Print -PassThru `
%{sleep 3;$_}
Remove-Item -Path C:\tests\*.*
This accomplishes what i need, i might put a kill process in after each one just to make sure there is no doc stuck.
But - The 1 thing left to accomplish is i need this to write to a non default printer and not change the existing default? This runs on a server with a vbscript already running utilizing default printer so it will mess that up if it changes.
As usual with COM Objects, it's pretty ugly to do.
You're supposed to do it like the question here, but I can't get that to work at all. $Inbox.Folders['FolderName'] returns nothing, and $Inbox.Folders('FolderName') returns an error.
Here's how I can get it to work. I'm on PowerShell v4 with Office 2013.
Say you've got a folder at \\Inbox\eReports\Amazon\StatsReports, and you want all the messages in it.
$Messages = $inbox.Folders `
| ? Name -eq 'eReports' `
| % Folders `
| ? Name -eq 'Amazon' `
| % Folders `
| ? Name -eq 'StatsReports' `
| % Items;
Note that the way I'm using ForEach-Object and Where-Object require PowerShell v3. Under earlier versions this would be considerably more verbose.
Here's a full version of the script that works for me on Win 7 x64/PowerShell v4/Office 2013. I've tested it on a few different folders, so it should work.
It will automatically rename files with duplicate names, following the Windows convention of adding a (2), then a (3) and so on.
$OutputFolder = 'C:\OutputFolder';
$outlook = New-Object -ComObject Outlook.Application;
$olFolderInbox = 6;
$ns = $outlook.GetNameSpace("MAPI");
$inbox = $ns.GetDefaultFolder($olFolderInbox);
$inbox.Folders `
| ? Name -eq 'eReports' `
| % Folders `
| ? Name -eq 'Amazon' `
| % Folders `
| ? Name -eq 'StatsReports' `
| % Items `
| % Attachments `
| % {
$OutputFileName = Join-Path -Path $OutputFolder -ChildPath $_.FileName;
if (Test-Path $OutputFileName) {
$FileDirectoryName = [System.IO.Path]::GetDirectoryName($OutputFileName);
$FileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($OutputFileName);
$FileExtension = [System.IO.Path]::GetExtension($OutputFileName);
for ($i = 2; Test-Path $OutputFileName; $i++) {
$OutputFileName = "{0} ({1}){2}" -f (Join-Path -Path $FileDirectoryName -ChildPath $FileNameWithoutExtension), $i, $FileExtension;
}
}
Write-Host $OutputFileName;
$_.SaveAsFile($OutputFileName);
}
Below is a script that monitors a directory and its subfolders for deposited files. Every 10 minutes or so, I look for new files and then match them against a database table that tell me where they need to be moved to - then it copies the files to a local archive, moves them to the locations they need to be moved to, and inserts a record into another database table with the file's attributes and where it came and went. If there is no match in the database - or there is an script error - it sends me an email.
However, since files are getting deposited to the directory constantly, it's possible that a file is still being written when the script executes. As a result, I get the error The process cannot access the file because it is being used by another process. emailed to me all the time. In addition, because I'm not dealing with the error up front; it goes through the loop and a false entry is inserted into my log table in the database with incorrect file attributes. When the file finally frees up, it gets inserted again.
I'm looking for a way to identify files that have processes attached to them; and skipping them when the script executes - but several days of web searches and some testing hasn't yielded an answer yet.
## CLEAR ERROR LOG
$error.clear()
Write-Host "***File Transfer Script***"
## PARAMETERS
$source_path = "D:\Files\In\"
$xferfail_path = "D:\Files\XferFailed\"
$archive_path = "D:\Files\XferArchive\"
$email_from = "SQLMail <SQLMail#bar.com>"
$email_recip = [STRING]"foo#bar.com"
$smtp_server = "email.bar.com"
$secpasswd = ConvertTo-SecureString "Pa$$w0rd" -AsPlainText -Force
$smtp_cred = New-Object System.Management.Automation.PSCredential ("BAR\SQLAdmin", $secpasswd)
## SQL LOG FUNCTION
function Run-SQL ([string]$filename, [string]$filepath, [int]$filesize, [int]$rowcount, [string]$xferpath)
{
$date = get-date -format G
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=SQLSERVER;Database=DATABASE;Uid=SQLAdmin;Pwd=Pa$$w0rd;"
$SqlConnection.Open()
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = "INSERT INTO DATABASE..Table VALUES ('$date','$filename','$filepath',$filesize,$rowcount,'$xferpath',0)"
$SqlCmd.Connection = $SqlConnection
$SqlCmd.ExecuteNonQuery()
$SqlConnection.Close()
}
## DETERMINE IF THERE ARE ANY FILES TO PROCESS
$file_count = Get-ChildItem -path $source_path |? {$_.PSIsContainer} `
| Get-ChildItem -path {$_.FullName} -Recurse | Where {$_.psIsContainer -eq $false} | Where {$_.Fullname -notlike "D:\Files\In\MCI\*"} `
| Measure-Object | Select Count
If ($file_count.Count -gt 0)
{
Write-Host $file_count.Count "File(s) Found - Processing."
Start-Sleep -s 5
## CREATE LIST OF DIRECTORIES
$dirs = Get-ChildItem -path $source_path -Recurse | Where {$_.psIsContainer -eq $true} | Where {$_.Fullname -ne "D:\Files\In\MCI"} `
| Where {$_.Fullname -notlike "D:\Files\In\MCI\*"}
## CREATE LIST OF FILES IN ALL DIRECTORIES
$files = ForEach ($item in $dirs)
{
Get-ChildItem -path $item.FullName | Where {$_.psIsContainer -eq $false} | Sort-Object -Property lastWriteTime -Descending
}
## START LOOPING THROUGH FILE LIST
ForEach ($item in $files)
{
## QUERY DATABASE FOR FILENAME MATCH, AND RETURN TRANSFER DIRECTORY
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=SQLSERVER;Database=DATABASE;Uid=SQLAdmin;Pwd=Pa$$w0rd;"
$SqlConnection.Open()
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = "SELECT F.DirTransfer FROM DATABASE..Files F WHERE '$item.Name.Trim()' LIKE F.FileName"
$SqlCmd.Connection = $SqlConnection
$DirTransfer = $SqlCmd.ExecuteScalar()
$SqlConnection.Close()
If ($DirTransfer) # if there is a match
{
Write-Host $item.FullName"`t->`t"$DirTransfer
$filename = $item.Name
$filepath = $item.FullName
$filesize = $item.Length
If (!($filesize))
{
$filesize = 0
}
$rowcount = (Get-Content -Path $item.FullName).Length
If (!($rowcount))
{
$rowcount = 0
}
$xferpath = $DirTransfer
Run-SQL -filename "$filename" -filepath "$filepath" -filesize "$filesize" -rowcount "$rowcount" -xferpath "$DirTransfer"
Copy-Item -path $item.FullName -destination $DirTransfer -force -erroraction "silentlycontinue"
Move-Item -path $item.FullName -destination $archive_path -force -erroraction "silentlycontinue"
#Write-Host "$filename $filepath $filesize $rowcount $xferpath"
}
Else # if there is no match
{
Write-Host $item.FullName "does not have a mapping"
Move-Item -path $item.FullName -destination $xferfail_path -force
$filename = $item.FullName
$email_body = "$filename `r`n`r`n does not have a file transfer mapping setup"
Send-MailMessage -To $email_recip `
-From $email_from `
-SmtpServer $smtp_server `
-Subject "File Transfer Error - $item" `
-Body $email_body `
-Priority "High" `
-Credential $smtp_cred
}
}
}
## IF NO FILES, THEN CLOSE
Else
{
Write-Host "No File(s) Found - Aborting."
Start-Sleep -s 5
}
## SEND EMAIL NOTIFICATION IF SCRIPT ERROR
If ($error.count -gt 0)
{
$email_body = "$error"
Send-MailMessage -To $email_recip `
-From $email_from `
-SmtpServer $smtp_server `
-Subject "File Transfer Error - Script" `
-Body $email_body `
-Priority "High" `
-Credential $smtp_cred
}
You can use the SysInternals handles.exe to find the open handles on a file. The exe can be downloaded from http://live.sysinternals.com/.
$targetfile = "C:\Users\me\Downloads\The-DSC-Book.docx"
$result = Invoke-Expression "C:\Users\me\Downloads\handle.exe $targetfile" | Select-String ([System.IO.Path]::GetFileNameWithoutExtension($targetfile))
$result
Outputs:
WINWORD.EXE pid: 3744 type: File 1A0: C:\Users\me\Downloads\The-DSC-Book.docx
Alternatively, you can check for errors either via try/catch or by looking at the $error collection after the Move-Item attempt then handle the condition appropriately.
$error.Clear()
Move-Item -path $item.FullName -destination $xferfail_path -force -ea 0
if($error.Count -eq 0) {
# do something useful
}
else {
# do something that doesn't involve spamming oneself
}
To expand on Arluin's answer. It fails if there's spaces in either the handle.exe or the $targetfile.
This will work for spaces in both and also formats the result to give you the Program Name.exe
$targetfile = "W:\Apps Folder\File.json"
$result = & "W:\Apps (Portable)\handle.exe" "$targetfile" | Select-String ([System.IO.Path]::GetFileNameWithoutExtension($targetfile))
$result = $result -replace '\s+pid\:.+'
$result
# PS> FreeCommander.exe
One way to avoid file locks caused by running the script on a timer is to use an event driven approach using a file system watcher. It has the ability to execute code when an event such as a new file is created in the folder you are monitoring.
To run code when the file is finished copying you would need to listen for the changed event. There is a slight issue with this event in that it fires once when the file begins copying and again when it is finished. I got an idea to work around this chicken/egg problem after checking out the module Mike linked to in the comments. I've updated the code below so that it will only fire off code when file has fully been written.
To try, change $folderToMonitor to the folder you want to monitor and add some code to process the file.
$processFile = {
try {
$filePath = $event.sourceEventArgs.FullPath
[IO.File]::OpenRead($filePath).Close()
#A Way to prevent false positive for really small files.
if (-not ($newFiles -contains $filePath)) {
$newFiles += $filePath
#Process $filePath here...
}
} catch {
#File is still being created, we wait till next event.
}
}
$folderToMonitor = 'C:\Folder_To_Monitor'
$watcher = New-Object System.IO.FileSystemWatcher -Property #{
Path = $folderToMonitor
Filter = $null
IncludeSubdirectories = $true
EnableRaisingEvents = $true
NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite'
}
$script:newFiles = #()
Register-ObjectEvent $watcher -EventName Changed -Action $processFile > $null