I'm currently writing a simple PowerShell script.
Basically, it should get the list of servers from a notepad and start to unzip the .zip file on each server and extract it to the new folder.
However, the script is not extracting all files under the zip file.
It would only extract one file from it and I'm not sure why the foreach loop not working properly.
Please shed some light on this issue. Thanks.
$servers = Get-Content "C:\tmp\script\new_unzip\servers.txt"
$Date = ((Get-Date).ToString('dd-MM-yyyy_HH-mm-ss'))
foreach ($server in $servers) {
$shell = new-object -com shell.application
$target_path = "\\$server\c$\Temp\FFPLUS_Temp"
$location = $shell.namespace($target_path)
$ZipFiles = Get-ChildItem -Path $target_path -Filter *.zip
$ZipFiles | Unblock-File
foreach ($ZipFile in $ZipFiles) {
$ZipFile.fullname | out-default
$NewLocation = "\\$server\c$\Temp\FFPLUS_Temp\$Date"
New-Item $NewLocation -type Directory -Force -ErrorAction SilentlyContinue
Move-Item $ZipFile.fullname $NewLocation -Force -ErrorAction SilentlyContinue
$NewZipFile = Get-ChildItem $NewLocation *.zip
$NewLocation = $shell.namespace($NewLocation)
$ZipFolder = $shell.namespace($NewZipFile.fullname)
$NewLocation.copyhere($ZipFolder.items())
}
}
$servers = Get-Content "C:\tmp\script\updated\servers.txt"
$Date = ((Get-Date).ToString('dd-MM-yyyy_HH-mm-ss'))
foreach ($server in $servers)
{
$zipFolder = "\\$server\c$\Temp\FFPLUS_Temp"
Add-Type -assembly System.IO.Compression.Filesystem
$zipFiles = Get-ChildItem -Path $zipFolder -Filter *.zip
foreach($zip in $zipFiles)
{
$destPath = "\\$server\c$\Temp\FFPLUS_Temp\$Date"
New-Item -ItemType Directory $destPath
[io.compression.zipfile]::ExtractToDirectory([string]$zip.FullName, "$destPath")
Move-Item $zip.fullname $destPath -Force -ErrorAction SilentlyContinue
}
}
Related
I am trying to get files from servers in a list using the below
$server = Get-Content server.txt
$server| ForEach-Object {
$session=new-pssession -computername $server -credential (Import-Clixml "mycredentials.xml")
Invoke-Command -Session $session -ScriptBlock ${function:getfiles}
Copy-Item -path "C:\some\folder\*" -Destination "C:\localfolder" -recurse -FromSession $session
}
If I supply explicitly a name in -computername, works like a charm.
When there are several names in the list, the execution stops after the first one. I suspect that the session closes after the first execution.
Is there a way to make it like this:
get-content -> for each line execute the copy-item -> close session -> open new session to new server -> .....etc, meaning that $session will be only for the current server.
$function:getfiles
function getfiles {
New-Item -Force -Path C:\path\trace.txt
$remoteserver=$env:computername
$trace='C:\path\trace.txt'
$Include = #('*.keystore', '*.cer', '*.crt', '*.pfx', '*.jks', '*.ks')
$exclude = '^C:\\(Windows|Program Files|Documents and Settings|Users|ProgramData)|\bBackup\b|\breleases?\b|\bRECYCLE.BIN\b|\bPerfLogs\b|\bold\b|\bBackups\b|\brelease?\b|'
Get-ChildItem -Path 'C:\','D:\' -file -Include $include -Recurse -EA 0|
Where-Object { $_.DirectoryName -notmatch $exclude } |
Select-Object -ExpandProperty FullName |
Set-Content -Path $trace
$des = "C:\some\folder\$remoteserver"
$safe = Get-Content $trace
$safe | ForEach-Object{
#find drive-delimeter
$first=$_.IndexOf(":\");
if($first -eq 1){
#stripe it
$newdes=Join-Path -Path $des -ChildPath #($_.Substring(0,1)+$_.Substring(2))[0]
}
else{
$newdes=Join-Path -Path $des -ChildPath $_
}
$folder=Split-Path -Path $newdes -Parent
$err=0
#check if folder exists"
$void=Get-Item $folder -ErrorVariable err -ErrorAction SilentlyContinue
if($err.Count -ne 0){
#create when it doesn't
$void=New-Item -Path $folder -ItemType Directory -Force -Verbose
}
$void=Copy-Item -Path $_ -destination $newdes -Recurse -Container -Verbose
}
}
UPDATE
So I have found out that the file where the lines should be be redirected from the script is not populated, which explains why the next step for copy-item fails. I have tried redirecting in different ways, still cant get it populated. The file is created without issues.
Made a workaround - placed the function in a script which is copied to the remote server / execute it \ clean afterwards.
Apologies, this question is scattered on the internet but I have yet to find a satisfactory answer that uses only Powershell 2.0 (with .NET v3.5) - no external libraries or programs
I'm using the following code to extract log.txt from ZipFile.zip (no matter log.txt's location)
$Destination = (new-object -com shell.application).NameSpace('C:\ZipExtractDir')
$ZipFile = (new-object -com shell.application).NameSpace('C:\ZipFile.zip')
$Destination.CopyHere(($Zipfile.Items() | where-object {$_.Name -like '*log.txt'}), 1044)
Works if log.txt is in directory root \log.txt
Fails if log.txt is in a subdirectory \Subfolder\log.txt
Fails if referencing the literal (.zip) path
{$_.Name -Like '*Subfolder\log.txt'} (both double & single quotes fail)
Have tried using -eq -like -contains '' "" $_.FullName
I'm quite certain that I'm filtering incorrectly - can anyone help with this code so that it will parse subdirectories as well?
Similar to what you have already done, you can set up the Shell.Application namespaces like this. Then you can copy the extracted directory to the destination path.
$zipFilePath = "Zipfile.zip"
$destinationPath = "C:\Users\Public\Downloads"
$zipfile = (New-Object -Com Shell.Application).NameSpace($zipFilePath)
$destination = (New-Object -Com Shell.Application).NameSpace($destinationPath)
$destination.CopyHere($zipfile.Items())
Then to list the log.txt files, we can contruct the full extracted path with Join-Path. This basically just appends the zip file name from System.IO.Path.GetFileNameWithoutExtension() to the destination path. Then just use Get-ChildItem to list the files recursively with the -Recurse and -Filter switches.
$extractedPath = Join-Path -Path $destinationPath -ChildPath ([System.IO.Path]::GetFileNameWithoutExtension($zipFilePath))
Get-ChildItem -Path $extractedPath -Filter log.txt -Recurse
And to test this for PowerShell 2.0 we can use -version 2 with powershell.exe:
powershell.exe -version 2 .\test.ps1
UPDATE
If you want to inspect files before extracting, you'll need to recurse the directories yourself. Below is a demo of how this can be done.
function New-ZipChildRootFolder
{
param
(
[string]$DestinationPath,
[string]$ZipFileName
)
$folderPath = Split-Path -Path $ZipFileName -Leaf
$destination = (New-Object -ComObject Shell.Application).NameSpace($DestinationPath)
$destination.NewFolder($folderPath)
}
function Get-ZipChildItems
{
param
(
[string]$ZipFilePath,
[string]$DestinationPath
)
$zipfile = (New-Object -ComObject Shell.Application).NameSpace($ZipFilePath)
$zipFileName = [System.IO.Path]::GetFileNameWithoutExtension($ZipFilePath)
Write-Output "Create root zip folder : $zipFileName"
New-ZipChildRootFolder -DestinationPath $DestinationPath -ZipFileName $zipFileName
foreach ($item in $zipFile.items())
{
Get-ZipChildItemsRecurse -Items $item -DestinationPath $DestinationPath -ZipFileName $zipFileName
}
}
function Get-ZipChildItemsRecurse
{
param
(
[object]$Items,
[string]$DestinationPath,
[string]$ZipFileName
)
foreach ($file in $Items.getFolder.Items())
{
if ($file.IsFolder -eq $true)
{
Write-Output "Creating folder : $($file.Path)"
New-ZipChildFolder -Folder $file -DestinationPath $DestinationPath -ZipFileName $ZipFileName
Get-ZipChildItemsRecurse -Items $file -DestinationPath $DestinationPath -ZipFileName $ZipFileName
}
else
{
$filename = Split-Path -Path $file.Path -Leaf
if ($filename -eq "log.txt")
{
Write-Output "Copying file : $($file.Path)"
New-ZipChildFile -File $file -DestinationPath $DestinationPath -ZipFileName $ZipFileName
}
}
}
}
function New-ZipChildFile
{
param
(
[object]$File,
[string]$DestinationPath,
[string]$ZipFileName
)
$destination = New-Object -ComObject Shell.Application
$items = $File.Path.Split("\")
$zipRootIndex = [array]::IndexOf($items, $ZipFileName)
$path = $items[$zipRootIndex..($items.Length - 2)] -join "\"
$fullPath = Join-path -Path $DestinationPath -ChildPath $path
$destination.NameSpace($fullPath).CopyHere($File)
}
function New-ZipChildFolder
{
param
(
[object]$Folder,
[string]$DestinationPath,
[string]$ZipFileName
)
$destination = New-Object -ComObject Shell.Application
$items = $Folder.Path.Split("\")
$zipRootIndex = [array]::IndexOf($items, $ZipFileName)
$folders = $items[$zipRootIndex..($items.Length - 1)]
$currentFolder = $DestinationPath
foreach ($folder in $folders)
{
$destination.NameSpace($currentFolder).NewFolder($folder)
$currentFolder = Join-Path -Path $currentFolder -ChildPath $folder
}
}
Usage:
$zipFilePath = "C:\Zipfile.zip"
$destinationPath = "C:\Users\Public\Downloads"
Get-ZipChildItems -ZipFile $zipFilePath -DestinationPath $destinationPath
As always, new to powershell and trying to self-teach myself. Thank you all in advance:
We have a logon script that auto-sets the registry to OUR homepage. When we build computers, we put the logon script inside the C:\Users\Default\%Appdata%\roaming....\startup\ folder. That way any new user that logs on gets the bat file put into their %AppData% folder, and their homepage is auto set.
We recently built a new server and due to some issues, we need to change our homepage URL, therefore needing to change the logon.bat file on all the computers for all user profiles.
This script I found on here works perfectly, but only on the local computer it is running on:
$source = '\\ITE00463866\Applications\_Layer1_Installs\TS Sector\firstlogon.txt'
$profilesfolder = 'c:\users\'
$excluded_profiles = #( 'All Users', 'Default User', 'Default.migrated', 'Public', 'DefaultAppPool', 'cdwuser', '.NET v4.5 Classic', '.NET v4.5')
$profiles = get-childitem $profilesfolder -Directory -force | Where-Object { $_.BaseName -notin $excluded_profiles }
$targetfolder = "\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
foreach ($profile in $profiles) {
$destination = $profilesfolder + $profile + $targetfolder
If ( $(Try { Test-Path $destination.trim() } Catch { $false }) ) {
copy-item -path $source -destination $destination -Force -Verbose
}
Else {
New-Item -Path $destination -ItemType Directory
copy-item -path $source -destination $destination -Force -Verbose
}
}
I have been trying to add the above ForEach statement INSIDE of a Get-Content | FOREACH($PC in $Computers){....} statement but get all these ODD issues and it only effects the local machine running the script on. For instance, taking every folder in my System32 folder and creating a user named whatever the System32 folder was named, then putting the logon.bat inside of all those %AppData% folders...
$source = '\\ITE00463866\Applications\_Layer1_Installs\TS Sector\firstlogon.txt'
$list = "\\ITE00463866\Applications\_Layer1_Installs\TS Sector\test.txt"
$computers = gc $list
foreach($pc in $computers){
$targetfolder = "\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
$excluded_profiles = #( 'Administrator', 'All Users', 'Default User', 'Default.migrated', 'Public', 'DefaultAppPool', 'cdwuser', '.NET v4.5 Classic', '.NET v4.5')
$profiles = get-childitem $profilesfolder -Directory -force | Where-Object { $_.BaseName -notin $excluded_profiles }
$profilesfolder = 'c:\users\'
foreach ($profile in $profiles) {
$destination = $profilesfolder + $profile + $targetfolder
if ( $(Try { Test-Path $destination.trim() } Catch { $false }) ) {
#If folder Startup folder is found for profile, add file to destionation, force overwrite
copy-item -path $source -destination $destination -Force -Verbose
}
Else {
#If folder is NOT found, create folder and move file to destination
New-Item -Path $destination -ItemType Directory
copy-item -path $source -destination $destination -Force -Verbose
}
}
}
How do I combine the two scripts to:
For each computer in my list, look in all the user profiles and for each profile (excluding the ones mentioned) add the new logon.bat
If the script u have posted at the top works for you and as long as winrm is configured in ur environment, you can enclose it inside a scriptblock like below:
$scriptblock = {
$source = '\\ITE00463866\Applications\_Layer1_Installs\TS Sector\firstlogon.txt'
$profilesfolder = 'c:\users\'
$excluded_profiles = #( 'All Users', 'Default User', 'Default.migrated', 'Public', 'DefaultAppPool', 'cdwuser', '.NET v4.5 Classic', '.NET v4.5')
$profiles = get-childitem $profilesfolder -Directory -force | Where-Object { $_.BaseName -notin $excluded_profiles }
$targetfolder = "\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
foreach ($profile in $profiles) {
$destination = $profilesfolder + $profile + $targetfolder
If ( $(Try { Test-Path $destination.trim() } Catch { $false }) ) {
copy-item -path $source -destination $destination -Force -Verbose
}
Else {
New-Item -Path $destination -ItemType Directory
copy-item -path $source -destination $destination -Force -Verbose
}
}
}
And then use ur foreach loop like so:
$list = "\\ITE00463866\Applications\_Layer1_Installs\TS Sector\test.txt"
$computers = gc $list
foreach($pc in $computers){
Invoke-Command -ComputerName $pc -ScriptBlock $scriptblock
}
Asked one of our principal programmers and he corrected my original script without needing to add a scipt block to it. Thank you for 1st suggestion!
Here is the final script that worked!
$source = '\\ITE00463866\Applications\_Layer1_Installs\TS Sector\firstlogon.bat'
$list = "\\ITE00463866\Applications\_Layer1_Installs\TS Sector\computers.txt"
$computers = gc $list
foreach($pc in $computers){
$targetfolder = "\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
$excluded_profiles = #( 'Administrator', 'All Users', 'Default User', 'Default.migrated', 'Public', 'DefaultAppPool', 'cdwuser', '.NET v4.5 Classic', '.NET v4.5')
$profilesfolder = '\\' + $pc + '\c$\users\'
$profiles = get-childitem $profilesfolder -Directory -force | Where-Object { $_.BaseName -notin $excluded_profiles }
foreach ($profile in $profiles) {
$destination = $profilesfolder + $profile + $targetfolder
if ( $(Try { Test-Path $destination.trim() } Catch { $false }) ) {
#If folder Startup folder is found for profile, add file to destionation, force overwrite
copy-item -path $source -destination $destination -Force -Verbose
}
Else {
#If folder is NOT found, create folder and move file to destination
New-Item -Path $destination -ItemType Directory
copy-item -path $source -destination $destination -Force -Verbose
}
}
}
I have a PowerShell script that moves all files from one location to another that have a date modified older than 3 years. I have it so the file when moved to the new location also keeps the file structure of the original.
I am trying to make it so once the file has been moved to the new location it creates a shortcut in the original directory which points to the new location of the file.
Below is my script so far which does all the above minus the shortcut.
$sourceDir = "C:\Users\bgough\Documents\powershell\docs"
$archiveTarget = "C:\Users\bgough\Documents\archive"
$dateToday = Get-Date
$date = $dateToday.AddYears(-3)
$items = Get-ChildItem $sourceDir -Recurse |
Where-Object {!$_.PSIsContainer -and $_.LastWriteTime -le $date}
foreach ($item in $items)
{
$withoutRoot = $item.FullName.Substring([System.IO.Path]::GetPathRoot($item.FullName).Length);
$destination = Join-Path -Path $archiveTarget -ChildPath $withoutRoot
$dir = Split-Path $destination
if (!(Test-Path $dir))
{
mkdir $dir
}
Move-Item -Path $item.FullName -Destination $destination
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$sourceDir")
$Shortcut.TargetPath = $destination
$Shortcut.Save()
}
In my script I have included my attempt at creating this shortcut but it hasn't helped. I have also read through the following but don't understand it too well..
How to create a shortcut using Powershell
Powershell Hard and Soft Links
Edit:
I have successfully got the shortcut to create and in the original folder. However, I can't seem to figure out how to pass a variable to use as the shortcut name. At the moment a string is hard coded, which is what the shortcut gets named. Please see code below: I would like to set the name as the item full name (Same name as document that was moved).
$sourceDir = "C:\Users\bgough\Documents\powershell\docs"
$archiveTarget = "C:\Users\bgough\Documents\archive"
$dateToday = Get-Date
$date = $dateToday.AddYears(-3)
$items = Get-ChildItem $sourceDir -recurse | Where-Object {!$_.PsIsContainer -and $_.LastWriteTime -le $date}
foreach ($item in $items)
{
$withoutRoot = $item.FullName.Substring([System.IO.Path]::GetPathRoot($item.FullName).Length);
$destination = Join-Path -Path $archiveTarget -ChildPath $withoutRoot
$dir = Split-Path $destination
if (!(Test-Path $dir))
{
mkdir $dir
}
Move-Item -Path $item.FullName -Destination $destination
$wshshell = New-Object -ComObject WScript.Shell
$desktop = [System.Environment]::GetFolderPath('Desktop')
$lnk = $wshshell.CreateShortcut($sourceDir + "\ShortcutName.lnk")
$lnk.TargetPath = "$destination"
$lnk.Save()
}
.lnk files are fine when you're using Explorer but they don't play well in Powershell or a command prompt.
What you need to do is create a symbolic link for the file. You can't do this in Powershell, but there is a command line utility called mklink that does it. I've wrapped it in a function so that you can call it:
function CreateLink
{
param
(
[string] $LinkName,
[string] $TargetFile
)
&"cmd.exe" /c mklink "$LinkName" "$TargetFile" | Out-Null
}
In your example you would call it like this:
CreateLink -LinkName $item.FullName -TargetFile $destination
When you look at the directory in Powershell the file will show up as being 0 bytes in size. Don't worry about that.
Thanks for your script Android Magic.
I have modified it to:
Copy a set of files from source to destination
It creates the identical folder structure on the destination, even if the folders are empty
It then creates a symbolic link to the archived file. SymbolicLink support was added in Powershell v5.1. You have to run the script as Admin in order for the Symbolic Link creation to work.
I'd like to add a function to email if anything goes wrong and a summary of status, but that's for another day.
$sourceDir = "\\Fileserver1\IT\Vendor"
$archiveTarget = "\\FS-ARCHIVE\Archive\Fileserver1\IT\Vendor"
$rootArchivePath = "\\FS-ARCHIVE\Archive"
$dateToday = Get-Date
$date = $dateToday.AddYears(-3)
# Copy folder structure to Archive
Get-ChildItem -Path $sourceDir -Recurse |
?{ $_.PSIsContainer } |
Copy-Item -Destination {Join-Path $archiveTarget $_.Parent.FullName.Substring($sourceDir.length)} -Force
$items = Get-ChildItem $sourceDir -Recurse -Attributes !Directory |
Where-Object {$_.LastAccessTime -le $date}
foreach ($item in $items)
{
$withoutRoot = Split-Path -Path $item.FullName
$destination = $rootArchivePath + $withoutRoot.Remove(0,1)
$destFile = $destination + "\" + $item
Move-Item -Force -Path $item.FullName -Destination $destination -Verbose
New-Item -ItemType SymbolicLink -Path $withoutRoot -Name $item -Value $destFile -Force -Verbose
}
I have the following problem: I am writing a loop that checks if some files appeared in a folder and if so then moves those files to another folder.
The script works nicely now, here is its code:
$BasePath = "C:\From"
$TargetPath = "C:\To"
$files = Get-ChildItem -File -Recurse -Path "$($BasePath)\$($Filename)" -ErrorAction SilentlyContinue
foreach ($file in $files)
{
$subdirectorypath = split-path $file.FullName.Replace($BasePath, "").Trim("\")
$targetdirectorypath = "$($TargetPath)\$($subdirectorypath)"
if ((Test-Path $targetdirectorypath) -eq $false)
{
Write-Host "Creating directory: $targetdirectorypath"
md $targetdirectorypath -Force
}
Write-Host "Copying file to: $($targetdirectorypath.TrimEnd('\'))\$($File.Name)"
Move-Item $File.FullName "$($targetdirectorypath.TrimEnd('\'))\$($File.Name)" -Force
}
However, as some of those files can be quite big, I would like to move those files asynchronously, in a "fire-and-forget" way. What is the best way to do it with powershell? This script will probably be running forever, so any asynchronous jobs would have to dispose themselves after they are done copying I think.
Thanks for suggestions
I would use a background job:
$scriptblock = {
$BasePath = $args[0]
$TargetPath = $args[1]
$files = Get-ChildItem -File -Recurse -Path "$($BasePath)\$($Filename)" -ErrorAction SilentlyContinue
foreach ($file in $files)
{
$subdirectorypath = split-path $file.FullName.Replace($BasePath, "").Trim("\")
$targetdirectorypath = "$($TargetPath)\$($subdirectorypath)"
if ((Test-Path $targetdirectorypath) -eq $false)
{
Write-Host "Creating directory: $targetdirectorypath"
md $targetdirectorypath -Force
}
Write-Host "Copying file to: $($targetdirectorypath.TrimEnd('\'))\$($File.Name)"
Move-Item $File.FullName "$($targetdirectorypath.TrimEnd('\'))\$($File.Name)" -Force
}
}
$arguments = #("C:\From","C:\To")
start-job -scriptblock $scriptblock -ArgumentList $arguments
If later you want to see any output from the job you can do the following
Get-Job | Receive-Job