I am using below script to get the details about port status of multiple remote servers.
Workflow Test-OpenPortWF
{
[CmdletBinding()]
param
(
[Parameter(Position=0)]
[String[]]$Target,
[Parameter(Mandatory=$true, Position=1)]
[int]$Port
)
If(Test-Path -Path C:\Temp\Results.csv -ErrorAction SilentlyContinue){ Remove-Item -Path C:\Temp\Results.csv -Force }
If(Test-Path -Path C:\Temp\Report.csv -ErrorAction SilentlyContinue){ Remove-Item -Path C:\Temp\Report.csv -Force }
foreach -parallel -throttle 50 ($t in $Target)
{
Sequence
{
$Out = Test-NetConnection -ComputerName $t -Port $Port -WarningAction SilentlyContinue | Select ComputerName,RemoteAddress,RemotePort,#{N="PortTestSucceeded"; E={$_.tcpTestSucceeded}}
Start-Sleep -Milliseconds 100
$Out | Export-Csv -Path C:\Temp\Results.csv -NoTypeInformation -Append
}
}
InlineScript
{
Import-Csv c:\Temp\Results.csv | Select ComputerName,RemoteAddress,RemotePort,PortTestSucceeded | Export-Csv c:\Temp\Report.csv -NoTypeInformation
Remove-Item c:\Temp\Results.csv -Force
Write-Host "Execution completed! Check Report.csv for output."
}
}
# Example use for multiple servers for one port 5985 and export results to CSV file.
# Assuming all target servers are found in c:\temp\Servers.txt (new line separated)
#
# PS C:\Temp> Test-OpenPortWF -Target (Get-Content .\Servers.txt) -Port 5985
Mostly it is working but it is not able to give complete results because since we are running this as a parallel workflow, if two servers complete the processing at the same time it will try to write the results to the CSV file at once for both the servers which is resulting in below error. And around 6% results are missing in the CSV file:
Microsoft.PowerShell.Utility\Write-Error : The process cannot access
the file 'C:\Temp\Results.csv' because it is being used by another
process. At Test-OpenPortWF:54 char:54
+
+ CategoryInfo : NotSpecified: (:) [Write-Error], CmdletInvocationException
+ FullyQualifiedErrorId : System.Management.Automation.CmdletInvocationException,Microsoft.PowerShell.Commands.WriteErrorCommand
+ PSComputerName : [localhost]
How can we get around this problem?
Because you use parallel processing, there may be conflicts when multiple threads try to output to your csv file (you run into file locks judging by the error)
Instead, try to output to single temporary files (with unique names) and at the end merge those files into one single report (and delete the temp files)
For example add a counter ($x) in the foreach loop that increments with every iteration ( $x++ ) , then output the results to -Path "C:\Temp\Results_$x.csv"
You need to use a mutex to lock the file I/O operation. Modify your Sequence as such:
Sequence
{
$Out = Test-NetConnection -ComputerName $t -Port $Port -WarningAction SilentlyContinue | Select ComputerName,RemoteAddress,RemotePort,#{N="PortTestSucceeded"; E={$_.tcpTestSucceeded}}
$mutex = New-Object System.Threading.Mutex $false, 'NetConnectionTest'
$mutex.WaitOne() > $null;
$Out | Export-Csv -Path C:\Temp\Results.csv -NoTypeInformation -Append
$mutex.ReleaseMutex();
}
Related
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 scriptblock I would like to run as a background job. Below is the command I would like to run:
Get-ChildItem -Recurse $source | Get-DfsrFileHash | Export-csv -Append C:\Temp\Test_Source_Checksum.csv
If I run this command it goes through successfully with no issues.
I have tried the following for 'Start-Job'
Start-Job -ScriptBlock { Get-ChildItem -Recurse $source | Get-DfsrFileHash | Export-csv -Append C:\Temp\Test_Source_Checksum.csv }
This results in 'Get-Job' displaying as completed, when it actually hasnt, or doesnt appear to have judging by the missing file: 'Test_Source_Checksum.csv'
I have also tried using the following for 'Invoke-Command'
Invoke-Command -AsJob -ComputerName ($env:COMPUTERNAME) -ScriptBlock { Get-ChildItem -Recurse $source | Get-DfsrFileHash | Export-csv -Append C:\Temp\Test_Source_Checksum.csv }
This results in 'Get-Job' displaying failed.
If I display the failure using:
(get-job -name Job38).JobStateInfo.Reason
I get nothing back...
Am I using Start-Job/Invoke-Command incorrectly here?
The reason I would like to run this as a background job is, im trying to copy large amounts of data and checksum it (for a DFS Migration). I would like to copy the data in smaller subsets, then checksum the data which has been copied whilst its copying the next batch over...rinse and repeat
Thanks,
Chris
EDIT:
Here is a copy of the whole script:
##----------------------------------------------------------------------------------##
$source="E:\DFSR_Migration_Test_Prod"
$dest="F:\DFSR_Migration_Test_Prod"
$what = #("/COPYALL","/B","/SEC","/E","/xd","dfsrprivate")
$options = #("/R:6","/tee","/MT:32")
$cmdArgs = #("$source","$dest",$what,$options)
##----------------------------------------------------------------------------------##
robocopy #cmdArgs
Write-Output "Prod_Copied #" (get-date) | Out-File C:\Temp\File_Copy.txt -Encoding ascii -Append -NoClobber
Write-Output "Initiating Prod Source Checksum #" (get-date) | Out-File C:\Temp\File_Copy.txt -Encoding ascii -Append -NoClobber
Start-Job -ScriptBlock { Get-ChildItem -Recurse $source | Get-DfsrFileHash | Export-csv C:\Temp\Prod_Source_Checksum.csv }
Write-Output "Initiating Prod Destination Checksum #" (get-date) | Out-File C:\Temp\File_Copy.txt -Encoding ascii -Append -NoClobber
Start-Job -ScriptBlock { Get-ChildItem -Recurse $dest | Get-DfsrFileHash | Export-csv C:\Temp\Prod_Destination_Checksum.csv }
How are you passing $source? Because if you're doing nothing other than writing the variable name it'll be null, and if it's null you'll be executing Get-ChildItem for $pwd.Path on the remote system.
The simplest solution is to make $source into $using:Source.
Invoke-Command -AsJob -ComputerName ($env:COMPUTERNAME) -ScriptBlock { Get-ChildItem -Recurse $using:source | Get-DfsrFileHash | Export-csv -Append C:\Temp\Test_Source_Checksum.csv }
In addition to the good information above, remember that starting a job is a lot like opening a new console (that you cannot see directly), this means that you may need to re-connect or re-authenticate. As an example, if you want to start a new job that uses PowerCLI, you will have to re-connect to your vCenter Server so in your scriptblock, you would preface your command with (connect-viserver...): start-job -scriptblock {connect-viserver -server <vCenterServer>; powercli command}.
I am having a bit of trouble with a PowerShell script. The intent of this is to spider the network and look for files/folders that exist on any PC.
Here is the original source:
#FiToFin Script#
$Fltr = "how_recover*.*"
$Online = "C:\Users\<username>\Scripts\Logs\Online.log"
$CSV = "C:\Users\<username>\Scripts\Devices.csv"
#$Tstpath = test-path "\\$computer\c$"
$Offline = "C:\Users\<username>\Scripts\Logs\Offline.log"
##################################################
$devices = Get-Content "$CSV"
foreach ($computer in $devices) {
Test-Path "\\$computer\c$" > $Tstpath
if ($Tstpath -eq $True) {
ls -Path "\\$computer\c$\users" -Filter $Fltr -Recurse |
Out-File -Append $Online
} else {
Write-Host "$computer is NOT Online" | Out-File -Append $Offline
}
}
##################################################
Write-Host "_____________________"
Write-Host "Online file = $Online"
Write-Host "Offile file = $Offline"
Write-Host "_____________________"
I have changed the if statement to if($Tstpath -eq "True"), if ($lastexitcode -eq $true) and if($Tstpath -eq $false) and they all just parse the first {Do command} no matter what. They never drop into else. Even tried the Tstpath = Test-Path \\$computer\c$ as a variable and just running that.
When it parses the first {Do Command} the return is
ls : Cannot find path '\\<computerName>\c$\u' because it does not exist.
At C:\Users\<username>\Scripts\FiToFin.ps1:19 char:3
+ ls -Path "\\$computer\c$\users" -Filter $Fltr -Recurse | Out-File -Append $On ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (\\<computername>\c$\u:String) [Get-ChildItem], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
What does work:
If my test machines are on I can ls -Path "\\$computer\c$\users" -Filter $Fltr -Recurse | Out-File -Append $Online just fine.
I get True or False from Test-Path \\$computer\c$ and can even > $var and Write-Host the results just fine.
I have no idea why this is and would love to know.
This also works:
###################################################################
$computer = "TestPC"
$Tstpath = Test-Path \\$computer\c$
####################################################################
$Tstpath > $null
if($Tstpath -eq $True) {
Write-Host "$computer is Online"
} else {
Write-Host "$computer is NOT Online"
}
But when you add the command ls or Get-ChildItem it freaks out.
So, question is: Why is it never executing the else portion?
I see two issues that would be causing your issues. How you initialize and update the variable $Tstpath
# Presumably Initialize
$Tstpath = test-path "\\$computer\c$"
# Updating in loop
test-path "\\$computer\c$" > $Tstpath
I will assume that you are testing in PowerShell ISE and that $Tstpath had a $true value at some point.
The issue is that you were never updating the variable. Looking at TechNet for about_redirection you will see that:
Operator Description Example
-------- ---------------------- ------------------------------
'>' Sends output to the Get-Process > Process.txt
specified file.
Your command was trying to output that to "file". You should have had an error about not being able to find the file or a file somewhere on your system with a single boolean in it (since it was not an append redirector).
What you should have done to stay with your logic is save the result via assignment.
$Tstpath = Test-path "\\$computer\c$"
Then you can test that.
However it is redundant since you do not ever need that value again. Would just be easier to put it straight in the if statement.
if(test-path "\\$computer\c$"){"Do something"}else{"Fail Trumpet"}
I would also suggest using Export-CSV -Append since you are dealing with objects. Would make for good structured output.
Get-ChildItem -path "\\$computer\c$\users\" -Filter $Fltr -Recurse | Export-CSV -Append $Online -NoTypeInformation
I created a PowerShell script to remove all files and folders older than X days. This works perfectly fine and the logging is also ok. Because PowerShell is a bit slow, it can take some time to delete these files and folders when big quantities are to be treated.
My questions: How can I have this script ran on multiple directories ($Target) at the same time?
Ideally, we would like to have this in a scheduled task on Win 2008 R2 server and have an input file (txt, csv) to paste some new target locations in.
Thank you for your help/advise.
The script
#================= VARIABLES ==================================================
$Target = \\share\dir1"
$OlderThanDays = "10"
$Logfile = "$Target\Auto_Clean.log"
#================= BODY =======================================================
# Set start time
$StartTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
Write-Output "`nDeleting folders that are older than $OlderThanDays days:`n" | Tee-Object $LogFile -Append
Get-ChildItem -Directory -Path $Target |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$OlderThanDays) } | ForEach {
$Folder = $_.FullName
Remove-Item $Folder -Recurse -Force -ErrorAction SilentlyContinue
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
# If folder can't be removed
if (Test-Path $Folder)
{ "$Timestamp | FAILLED: $Folder (IN USE)" }
else
{ "$Timestamp | REMOVED: $Folder" }
} | Tee-Object $LogFile -Append # Output folder names to console & logfile at the same time
# Set end time & calculate runtime
$EndTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
$TimeTaken = New-TimeSpan -Start $StartTime -End $EndTime
# Write footer to log
Write-Output ($Footer = #"
Start Time : $StartTime
End Time : $EndTime
Total runtime : $TimeTaken
$("-"*79)
"#)
# Create logfile
Out-File -FilePath $LogFile -Append -InputObject $Footer
# Clean up variables at end of script
$Target=$StartTime=$EndTime=$OlderThanDays = $null
One way to achieve this would be to write an "outer" script that passes the directory-to-be-cleaned, into the "inner" script, as a parameter.
For your "outer" script, have something like this:
$DirectoryList = Get-Content -Path $PSScriptRoot\DirList;
foreach ($Directory in $DirectoryList) {
Start-Process -FilePath powershell.exe -ArgumentList ('"{0}\InnerScript.ps1" -Path "{1}"' -f $PSScriptRoot, $Directory);
}
Note: Using Start-Process kicks off a new process that is, by default, asynchronous. If you use the -Wait parameter, then the process will run synchronously. Since you want things to run more quickly and asynchronously, omitting the -Wait parameter should achieve the desired results.
Invoke-Command
Alternatively, you could use Invoke-Command to kick off a PowerShell script, using the parameters: -File, -ArgumentList, -ThrottleLimit, and -AsJob. The Invoke-Command command relies on PowerShell Remoting, so that must enabled, at least on the local machine.
Add a parameter block to the top of your "inner" script (the one you posted above), like so:
param (
[Parameter(Mandatory = $true)]
[string] $Path
)
That way, your "outer" script can pass in the directory path, using the -Path parameter for the "inner" script.
I want the below code to work on multiple computers - any idea how to do this? I have the below but it fails as I do not currently call in the servers in question I think.
Thanks,
CODE:
Write-Host "Script to check Storage Write, Read and Delete Times"
Write-Host "`n"
$computer = Get-Content -path d:\temp\servers.txt
$path = "f:\temp\test.txt"
Foreach ($storage in $computer)
{
$date = Get-Date
Write-Host "Script being run on $date"
$write = Measure-Command { new-item -Path $path -ItemType File -Force } | select TotalMilliseconds
Write-Host "Writing file on $storage took $write"
$read = Measure-Command { Get-Content -Path $path } | select TotalMilliseconds
Write-Host "Reading file on $storage took $read"
$delete = Measure-Command {Remove-Item -Path $path -Force } | select TotalMilliseconds
Write-Host "Deleting file on $storage took $delete"
Write-Host "`n"
}
You need to step back for a second an rethink the approach. You are issuing the filesystem commands every time to f:\temp, which is on your local system.
There are two ways to make remote computers perform filesystem tasks. The easiest way is to use UNC paths. That is, \\server\share format. Assuming you have local admin access:
Foreach ($storage in $computer) {
$uncpath = $("\\{0}\f`$\temp\text.txt" -f $storage)
$write = Measure-Command { new-item -Path $uncpath -ItemType #...
# rest of code uses $uncpath for access
}
Mind you, using UNC path puts some stess on LAN, so this type of testing might or might not be accurate enough.
The second way would be using Powershell remoting to connect on remote systems and issuing the commands there. Take a look at New-PSSession, Enter-PSSession and Exit-PSSession cmdlets.