This identical code has been used in 3 servers, and only one of them does it silently fail to move the items (it still REMOVES them, but they do not appear in the share).
Azure-MapShare.ps1
param (
[string]$DriveLetter,
[string]$StorageLocation,
[string]$StorageKey,
[string]$StorageUser
)
if (!(Test-Path "${DriveLetter}:"))
{
cmd.exe /c "net use ${DriveLetter}: ${StorageLocation} /u:${StorageUser} ""${StorageKey}"""
}
Get-Exclusion-Days.ps1
param (
[datetime]$startDate,
[int]$daysBack
)
$date = $startDate
$endDate = (Get-Date).AddDays(-$daysBack)
$allDays =
do {
"*"+$date.ToString("yyyyMMdd")+"*"
$date = $date.AddDays(-1)
} until ($date -lt $endDate)
return $allDays
Migrate-Files.ps1
param(
[string]$Source,
[string]$Filter,
[string]$Destination,
[switch]$Remove=$False
)
#Test if source path exist
if((Test-Path -Path $Source.trim()) -ne $True) {
throw 'Source did not exist'
}
#Test if destination path exist
if ((Test-Path -Path $Destination.trim()) -ne $True) {
throw 'Destination did not exist'
}
#Test if no files in source
if((Get-ChildItem -Path $Source).Length -eq 0) {
throw 'No files at source'
}
if($Remove)
{
#Move-Item removes the source files
Move-Item -Path $Source -Filter $Filter -Destination $Destination -Force
} else {
#Copy-Item keeps a local copy
Copy-Item -Path $Source -Filter $Filter -Destination $Destination -Force
}
return $True
The job step is type "PowerShell" on all 3 servers and contains this identical code:
#Create mapping if missing
D:\Scripts\Azure-MapShare.ps1 -DriveLetter 'M' -StorageKey "[AzureStorageKey]" -StorageLocation "[AzureStorageAccountLocation]\backup" -StorageUser "[AzureStorageUser]"
#Copy files to Archive
D:\Scripts\Migrate-Files.ps1 -Source "D:\Databases\Backup\*.bak" -Destination "D:\Databases\BackupArchive"
#Get date range to exclude
$exclusion = D:\Scripts\Get-Exclusion-Days.ps1 -startDate Get-Date -DaysBack 7
#Remove items that are not included in exclusion range
Remove-Item -Path "D:\Databases\BackupArchive\*.bak" -exclude $exclusion
#Move files to storage account. They will be destroyed
D:\Scripts\Migrate-Files.ps1 -Source "D:\Databases\Backup\*.bak" -Destination "M:\" -Remove
#Remove remote backups that are not from todays backup
Remove-Item -Path "M:\*.bak" -exclude $exclusion
If I run the job step using SQL then the files get removed but do not appear in the storage account. If I run this code block manually, they get moved.
When I start up PowerShell on the server, I get an error message: "Attempting to perform the InitializeDefaultDrives operation on the 'FileSystem' provider failed." However, this does not really impact the rest of the operations (copying the backup files to BackupArchive folder, for instance).
I should mention that copy-item also fails to copy across to the share, but succeeds in copying to the /BackupArchive folder
Note sure if this will help you but you could try to use the New-PSDrive cmdlet instead of net use to map your shares:
param (
[string]$DriveLetter,
[string]$StorageLocation,
[string]$StorageKey,
[string]$StorageUser
)
if (!(Test-Path $DriveLetter))
{
$securedKey = $StorageKey | ConvertTo-SecureString -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential ($StorageUser, $securedKey)
New-PSDrive -Name $DriveLetter -PSProvider FileSystem -Root $StorageLocation -Credential $credentials -Persist
}
Apparently I tricked myself on this one. During testing I must have run the net use command in an elevated command prompt. This apparently hid the mapped drive from non-elevated OS features such as the Windows Explorer and attempts to view its existence via non-elevated command prompt sessions. I suppose it also was automatically reconnecting during reboots because that did not fix it.
The solution was as easy as running the net use m: /delete command from an elevated command prompt.
Related
I am trying to schedule a PowerShell Core 7.2 script to run on Windows Server 2012R2.
The script runs manually, without any errors, from the server and the Task Scheduler runs the task. In the History, I can see Task Completed
The issue is that the script is not executed. It is supposed to move the files and files are not moving which means the script was not executed.
Settings of the Task Scheduler that are selected are as follows:
General - Run whether user is logged on or not, Run with the highest privileges.
Actions -> Action Start a Program
Actions -> Program/Script "C:\Program Files\PowerShell\7\pwsh.exe" (location of pwsh.exe)
Actions -> Add Arguments -Executionpolicy Bypass -File "R:\Public\IT\Vantage_Utilities\CNC_Scripts\File Transfer\Fastems\CNC_File_Transfer_Fastems.ps1"
Location -> Name of the local machine
I am not really sure what is going wrong here.
EDIT
I am thinking there is an issue with the script. Because there is another script set up to be executed with PS Core and Task Scheduler. I am going to post the script here. It is a simple batch file that moves all the contents of one folder from one server to another. I achieve this in two functions. Function MoveFiles moves all the contents of the parent folder(excluding the subfolder called "Mazak"). The second function,Function MoveMazakFiles function moves the contents of "Mazak" only. ( I am completely aware I could have done this using fewer lines of code but that is not the point here)
Code:
$logPath = "\\MMS25163S1\Public\IT\Vantage_Utilities\CNC_Scripts\File Transfer\Fastems\Log.txt"
$trancriptPath = "\\MMS25163S1\Public\IT\Vantage_Utilities\CNC_Scripts\File Transfer\Fastems\LogTranscript.txt"
$getDate = Get-Date -Format "dddd MM/dd/yyyy HH:mm "
$counter = 0
$mazakCounter = 0
Start-Transcript -Path $trancriptPath -Append
Add-Content -Path $logPath -Value ("LOG CREATED $getDate") -PassThru
#Sources
$srcMca = "\\MMS25163S1\Public\NcLib\FromNC\*"
$srcMcaNameChg ="\\MMS25163S1\Public\NcLib\FromNC"
$srcMazak= "\\MMS25163S1\Public\NcLib\FromNC\Mazak\*"
$srcMcaNameChgMazak = "\\MMS25163S1\Public\NcLib\FromNC\Mazak"
#Destination
$destMca = "\\Sidney2\MfgLib\RevisedPrograms\MC-A"
#Time with milliseconds
$time = (Get-Date -Format hh-mm-fff-tt).ToString()
Function MoveFiles{
Param(
[string]$src,
[string]$dest,
[string]$srcNameChange
)
Get-Item -Path $src -Exclude *Mazak* -ErrorAction SilentlyContinue | ForEach-Object{
$counter++
$fileName = $_.BaseName
$fileNameExt = $_.Name
Write-host $fileName -ForegroundColor Green
Rename-Item -Path "$srcMcaNameChg\$fileNameExt" -NewName ($fileName+"_"+"(Time-$time)"+$_.Extension);
Add-Content -Path $logPath -Value ("Name changed: Time stamp added to $fileName ") -PassThru
}
Move-Item -Path $src -Exclude *Mazak* -Destination $dest -Force
Add-Content -Path $logPath -Value ("$counter file(s) moved to $dest") -PassThru
}
MoveFiles -src $srcMca -dest $destMca -srcNameChange $srcMcaNameChg
Function MoveMazakFiles{
Param(
[string]$srcMazak,
[string]$dest,
[string]$srcNameChange
)
Get-ChildItem $srcMazak -Recurse -ErrorAction SilentlyContinue | ForEach-Object{
$mazakCounter++
$fileName = $_.BaseName
$fileNameExt = $_.Name
Write-host $fileName -ForegroundColor Green
Rename-Item -Path "$srcMcaNameChgMazak\$fileNameExt" -NewName ($fileName+"_"+"(Time-$time)"+$_.Extension);
}
Move-Item -Path $srcMazak -Destination $dest -Force
Add-Content -Path $logPath -Value ("$mazakCounter file(s) from Mazak folder moved to $dest") -PassThru
}
MoveMazakFiles -srcMazak $srcMazak -dest $destMca -srcNameChange $srcMcaNameChg
Stop-Transcript
When setting the scheduled task, under Action -> Start a Program -> Program/Script. Call powershell and pass the script as the parameter
Like
powershell -File "R:\Public\IT\Vantage_Utilities\CNC_Scripts\File Transfer\Fastems\CNC_File_Transfer_Fastems.ps1"
I'm new to Powershell. I have 80 servers that I need to connect to and run a Pshell script on remotely to find files recursively in one share by last access date and move them to another \server\share for archiving purposes. I also need the file creation, last accessed etc. timestamps to be preserved.
I would welcome any help please
thank you
You need to test this thoroughly before actually using it on all 80 servers!
What you could do if you want to use PowerShell on this is to use Invoke-Command on the servers adding admin credentials so the script can both access the files to move as well as the destination Archive folder.
I would suggest using ROBOCOPY to do the heavy lifting:
$servers = 'Server1', 'Server2', 'Server3' # etcetera
$cred = Get-Credential -Message "Please supply admin credentials for archiving"
$scriptBlock = {
$SourcePath = 'D:\StuffToArchive' # this is the LOCAL path on the server
$TargetPath = '\\NewServer\ArchiveShare' # this is the REMOTE path to where the files should be moved
$LogFile = 'D:\ArchivedFiles.txt' # write a textfile with all fie fullnames that are archived
$DaysAgo = 130
# from a cmd box, type 'robocopy /?' to see all possible switches you might want to use
# /MINAGE:days specifies the LastWriteTime
# /MINLAD:days specifies the LastAccessDate
robocopy $SourcePath $TargetPath /MOVE /MINLAD:$DaysAgo /COPYALL /E /FP /NP /XJ /XA:H /R:5 /W:5 /LOG+:$logFile
}
Invoke-Command -ComputerName $servers -ScriptBlock $scriptBlock -Credential $cred
If you want to do all using just PowerShell, try something like this:
$servers = 'Server1', 'Server2', 'Server3' # etcetera
$cred = Get-Credential -Message "Please supply admin credentials for archiving"
$scriptBlock = {
$SourcePath = 'D:\StuffToArchive' # this is the LOCAL path on the server
$TargetPath = '\\NewServer\ArchiveShare' # this is the REMOTE path to where the files should be moved
$LogFile = 'D:\ArchivedFiles.txt' # write a textfile with all fie fullnames that are archived
$refDate = (Get-Date).AddDays(-130).Date # the reference date set to midnight
# set the ErrorActionPreference to Stop, so exceptions are caught in the catch block
$OldErrorAction = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
# loop through the servers LOCAL path to find old files and move them to the remote archive
Get-ChildItem -Path $SourcePath -File -Recurse |
Where-Object { $_.LastAccessTime -le $refDate } |
ForEach-Object {
try {
$target = Join-Path -Path $TargetPath -ChildPath $_.DirectoryName.Substring($SourcePath.Length)
# create the folder in the archive if not already exists
$null = New-Item -Path $target -ItemType Directory -Force
$_ | Move-Item -Destination $target -Force
Add-Content -Path $LogFile -Value "File '$($_.FullName)' moved to '$target'"
}
catch {
Add-Content -Path $LogFile -Value $_.Exception.Message
}
}
$ErrorActionPreference = $OldErrorAction
}
Invoke-Command -ComputerName $servers -ScriptBlock $scriptBlock -Credential $cred
I'm struggling to understand how PowerShell handles recursion and Copy-Item command.
$date=Get-Date -Format yyyyMMdd
$oldfolder="c:\certs\old\$date"
New-PSDrive -Name "B" -PSProvider FileSystem -Root "\\(server)\adconfig"
$lastwrite = (get-item b:\lcerts\domain\wc\cert.pfx).LastWriteTime
$timespan = new-timespan -days 1 -hours 1
Write-Host "testing variables..."
Write-Host " date = $date" `n "folder path to create = $oldfolder" `n
"timespan = $timespan"
if (((get-date) - $lastwrite) -gt $timespan) {
#older
Write-Host "nothing to update."
}
else {
#newer
Write-Host "newer certs available, moving certs to $oldfolder"
copy-item -path "c:\certs\wc" -recurse -destination $oldfolder
copy-item b:\lcerts\domain\wc\ c:\certs\ -recurse -force
}
Existing files exist at c:\certs\wc\cert.pfx
I have the "test" comparing the time between the cert.pfx in the b:\lcerts\domain\wc\ folder and the current time . If the cert has been modified in the past 1 day and 1 hour, then the script should continue:
Copy cert.pfx from c:\certs\wc\ to c:\certs\old\$date\cert.pfx
Copy cert.pfx from b:\lcerts\domain\wc to c:\certs\wc\cert.pfx
I obviously don't understand PowerShell nomenclature for this because the first time I run this script, it works fine. The second time it creates another folder inside c:\certs\wc\$date\wc\cert.pfx.
How do I get it to fail with "c:\certs\wc\$date\cert.pfx already exists?"
I don't want to restrict this to just the cert.pfx file by specifying the actual file name, I want all files in the folder as eventually there will be more than one file.
The behavior of Copy-Item when a directory is specified in the -Path parameter depends on whether the directory specified in the -Destination parameter exists.
Copy-Item -Path "c:\certs\wc" -Recurse -Destination "c:\certs\old\$date"
If the c:\certs\old\$date directory does not exist, then the wc directory is copied and named c:\certs\old\$date.
If the c:\certs\old\$date directory exists, the wc directory is copied under the c:\certs\old\$date directory. Therefore, it becomes c:\certs\old\$date\wc.
So you are sure to check in advance if the directory exists.
if(Test-Path $oldfolder) { throw "'$oldfolder' is already exists." }
Copy-Item -Path "c:\certs\wc" -Destination $oldfolder -Recurse
You are not testing if the destination folder exists. Seeing you are creating its name using a current date, it is more than likely this folder does not yet exist, so you need to create it first.
Also, There should be no need to use the New-PSDrive cmdlet, because Copy-Item is perfectly able to use UNC paths.
Something like this perhaps:
$server = '<NAME OF THE SERVER>'
$serverPath = "\\$server\adconfig\lcerts\domain\wc"
$testFile = Join-Path -Path $serverPath -ChildPath 'cert.pfx'
$localPath = 'c:\certs\wc'
$date = Get-Date -Format yyyyMMdd
$timespan = New-TimeSpan -Hours 1 -Minutes 1
$oldfolder = "c:\certs\old\$date"
# check if this output path exists. If not, create it
if (!(Test-Path -Path $oldfolder -PathType Container)) {
Write-Host "Creating folder '$oldfolder'"
New-Item -ItemType Directory -Path $oldfolder | Out-Null
}
Write-Host "testing variables..."
Write-Host "date = $date`r`nfolder path to create = $oldfolder`r`ntimespan = $timespan"
# test the LastWriteTime property from the cert.pfx file on the server
$lastwrite = (Get-Item $testFile).LastWriteTime
if (((Get-Date) - $lastwrite) -gt $timespan) {
#older
Write-Host "Nothing to update."
}
else {
#newer
Write-Host "Newer cert(s) available; copying all from '$localPath' to '$oldfolder'"
Copy-Item -Path $localPath -Filter '*.pfx' -Destination $oldfolder
Copy-Item -Path $serverPath -Filter '*.pfx' -Destination $localPath -Force
}
I have implemented a PS Script that deploys code on multiple servers at the same time. Here I need to copy some source file from one server to another. See the code below:
for ($i=1; $i -le 5; $i++) {
$serverName="iwflO" + $i
$sourceFile="\\iwdflO1\C$\Deploy\bin"
$destination="\\$serverName\C$\Program Files (X86)\Shian\MyService\bin\"
$Myblock = {
Param{$sourceFile,$destination)
Copy-Item -Force -Recurse $sourceFile -Destination $destination
}
$result = Invoke-Command -ComputerName $ServerName -Credential "shian" -ScriptBlock $Myblock -ArgumentList $sourceFile,$destination;
$result;
}
cd c:\
It's working fine for iwflO1 which is the root server from where I'm running the script but for other servers it's giving me an error like
Cannot find Path "\iwdflO1\C$\Deploy\bin" because it does not exist.
But if I logged in to iwflO2 or any other server and hit the path manually its working fine.
I can see the mistake is with the block :
Instead of this:
$Myblock={param{$sourceFile,$destination)
copy-Item -Force -Recurse $sourceFile -Destination $destination
}
Do this:
$Myblock={param($sourceFile,$destination)
copy-Item -Force -Recurse $sourceFile -Destination $destination
}
This is working fine if I am hardcoding the server names(tested in my local)
Since you are using admin share, directly try this:
Copy-Item -Path \\serverA\c$\programs\temp\test.txt -Destination \\serverB\c$\programs\temp\test.txt;
Note: You have to specify the file. Else you get-childitem -recurse inside the source folder and put it directly in the destination .
Hope it helps.
I'm trying to copy a license file to all domain joined computers,
and i am only able to use PowerShell.
GPO or even (GPO) scheduled task is a no-go, because these don't seem to be possible yet within a Server 2003 environment.
The application this is regarding is already installed, and only the license file needs to be overwritten.
i am hoping to achieve:
- check if online, else skip
- check for 32b folder location, if exist, copy, else skip
- check for 64b folder location, if exist copy, else skip
- write every computers' result to a log file so i can prune successful
1st attempt;
Code i have currently:
$computers = gc "c:\temp\computers.txt"
$source = "c:\temp\almmodule.lic"
$dest32b = 'c$\program files (x86)\ALM - Automatic Login Module'
$dest64b = 'c$\program files\ALM - Automatic Login Module'
$TestPath32 = Test-Path -path "\\$computer\$dest32b\*"
$TestPath64 = Test-Path -path "\\$computer\$dest64b\*"
foreach ($computer in $computers) {
if (test-Connection -Cn $computer -quiet) {
IF (!$TestPath32) {
Copy-Item $source -Destination "\\$computer\$dest32b" -recurse -force -verbose}
ELSE {
"$computer' 32b folder not found. Skipping"}
IF (!$TestPath64) {
Copy-Item $source -Destination "\\$computer\$dest64b" -recurse -force -verbose}
ELSE
{"$computer 64b folder not found. Skipping"}
ELSE {
"$computer is not online"
}
}
}
I've tried some minor variations, but i can't seem to get anywhere.
Also the logging needs yet to be created.
Using myself as a test target, having run above variables, and using single commands;
$TestPath32 = Test-Path -path "\$computer\$dest32b*"
Returns: True
Copy-Item $source -Destination "\$computer\$dest32b" -recurse -force -verbose
is successful, and copies the file
Running the PowerShell at the moment complains about the last ELSE statement.
But most of the time it failed not recognizing i don't have a 64b folder, and it either gives an error, or places a file, with the directory as the filename.
I'm at a loss and have now tried so many things i'm afraid of braking the little that i've got.
2nd attempt;
I have edited the code and commented out some parts, just to get a working > model to progress from there.
foreach ($computer in $computers) {
$TestPath32 = Test-Path "\\$computer\$dest32b\*"
$TestPath64 = Test-Path "\\$computer\$dest64b\*"
# if (test-Connection -Cn $computer -quiet) {
IF (!$TestPath32) {
Copy-Item $source -Destination "\\$computer\$dest32b\" -recurse -force -verbose}
ELSE
{"$computer' 32b folder not found. Skipping"}
IF (!$TestPath64) {
Copy-Item $source -Destination "\\$computer\$dest64b\" -recurse -force -verbose}
ELSE
{"$computer 64b folder not found. Skipping"}
# ELSE {
# "$computer is not online"
# }
#}
}
Now returns :
PS C:\windows\system32> C:\Temp\ALM 2016 LIC copy.ps1
L2016010' 32b folder not found. Skipping
VERBOSE: Performing operation "Copy File" on Target "Item: C:\temp\almmodule.lic Destination: \\L2016010\c$\program files\ALM - Automatic Login Module\".
Copy-Item : De syntaxis van de bestandsnaam, mapnaam of volumenaam is onjuist.
At C:\Temp\ALM 2016 LIC copy.ps1:14 char:12
+ Copy-Item <<<< $source -Destination "\\$computer\$dest64b\" -force -verbose}
+ CategoryInfo : NotSpecified: (:) [Copy-Item], IOException
+ FullyQualifiedErrorId : System.IO.IOException,Microsoft.PowerShell.Commands.CopyItemCommand
i can assure you i DO have the 32b path. The copy-item doesn't place the file.
i DO NOT have the 64b path obviously, and i feel it breaks on this instead of just neatly returning $false like it should.
When i mess arround with he Path (because i thought that was the reason for the failure), it sometimes places a file in Program Files named "ALM - Automatic Login Module" which is the size of the license file.
Again, if i run the line Copy-Item $source -Destination "\\$computer\$dest32b\" as stand-alone, it DOES copy the file.
3rd attempt, now working;
$computers = gc "c:\temp\computers.txt"
$source = "c:\temp\almmodule.lic"
$dest32b = 'c$\program files (x86)\ALM - Automatic Login Module'
$dest64b = 'c$\program files\ALM - Automatic Login Module'
foreach ($computer in $computers) {
$TestUp = test-Connection -Cn $computer -quiet
$TestPath32 = Test-Path -pathType container "\\$computer\$dest32b"
$TestPath64 = Test-Path -pathType container "\\$computer\$dest64b"
IF (!$TestUp) {
write-host "$computer is not online"
} ELSE {
IF (!$TestPath32){
write-host "$computer' 32b folder not found. Skipping"
} ELSE {
Copy-Item $source -Destination "\\$computer\$dest32b\" -force -verbose
}
IF (!$TestPath64){
write-host "$computer 64b folder not found. Skipping"
} ELSE {
Copy-Item $source -Destination "\\$computer\$dest64b\" -force -verbose
}
}
}
The use of !$ totally went over my head, and i was supposed to work from a point of:
IF NOT, THEN, ELSE
Now the script skips folders not present, havn't been able to test a down computer yet, but i assume this now works also.
Now all i need to figure out, is how to log the output into 1 logfile, in a readable format
While using a GPO or a scheduled task would be better, or possibly deploying with SCCM if you've got it, there is an answer within your constraints. You've got the right basic idea, but move your assignments of $TestPath32 and $TestPath64 into the foreach loop:
...
foreach ($computer in $computers) {
if (test-Connection -Cn $computer -quiet) {
$TestPath32 = Test-Path -path "\\$computer\$dest32b\*"
$TestPath64 = Test-Path -path "\\$computer\$dest64b\*"
IF (!$TestPath32) {
...
I vote for GPO as well, as mentioned in the comments, you should definitely consider that.
However, if it doesn't help you,
Your $TestPath32 & $TestPath64 variables are evaluated before you assign your individual computer values. I would simply be null, and hence both will be $false. You can simply move them inside your for-loop,
foreach ($computer in $computers) {
if (test-Connection -Cn $computer -quiet) {
$TestPath32 = Test-Path -path "\\$computer\$dest32b\*"
$TestPath64 = Test-Path -path "\\$computer\$dest64b\*"
......
}
Also in the other hand I hope you have formatted the content of your file to return an array. If not you simply delimit it with "," and read
File Content: Computer1,Computer2,Computer3
and read the file as an array directly
$computers = Get-Content 'c:\temp\computers.txt' # It will read it as an array only. YOu do not need any additional formatting in this case.
Final version.
Couldn't get out-file to work like i wanted to.
Resorted to Start-Transcript.
Had to put Get-Content at the end to format the output to something that was readable.
#set-executionpolicy RemoteSigned
$computers = gc "c:\temp\computers.txt"
$source = "c:\temp\almmodule.lic"
$dest32b = 'c$\program files (x86)\ALM - Automatic Login Module'
$dest64b = 'c$\program files\ALM - Automatic Login Module'
Start-Transcript -path C:\temp\ALM-Log.txt -append
foreach ($computer in $computers) {
$TestUp = test-Connection -Cn $computer -quiet
$TestPath32 = Test-Path -pathType container "\\$computer\$dest32b"
$TestPath64 = Test-Path -pathType container "\\$computer\$dest64b"
IF (!$TestUp) {
write-host "$computer is not online`r"
} ELSE {
IF (!$TestPath32){
write-host "$computer' 32b folder not found. Skipping`r"
} ELSE {
Copy-Item $source -Destination "\\$computer\$dest32b\" -force -verbose
}
IF (!$TestPath64){
write-host "$computer 64b folder not found. Skipping`r"
} ELSE {
Copy-Item $source -Destination "\\$computer\$dest64b\" -force -verbose
}
}
}
Stop-Transcript
$log = Get-Content C:\temp\ALM-Log.txt
$log > c:\temp\ALM-Log.log
The actual solution as to why this works = Test-Path -pathtype container