I know this is probably a very basic question and should exist in the PowerShell documentation, but the point is that I don't exactly know how to look up the answer to this.
The question is: how to prevent a variable from being applied before being requested in code?
Example:
#Variable
$MoveToTest = Move-Item -Path "C:\Test.jpg" -Destination "C:\Test Folder" -force -ea 0
#Code
If (Test-Path C:\*.mp4) {
$MoveToTest
}
The If condition asks for the .jpg file to be moved if there is any .mp4 file in the -Path, however the way the code is written the variable is applied before availing the If condition.
Sorry, I know this is probably very basic, but as I'm learning by myself and according to everyday needs, I ended up missing some basic principles.
You can defer execution of a piece of code by wrapping it in a ScriptBlock {...}:
# Variable now contains a scriptblock with code that we can invoke later!
$MoveToTest = { Move-Item -Path "C:\Test.jpg" -Destination "C:\Test Folder" -force -ea 0 }
# Code
If (Test-Path C:\*.mp4) {
# Use the `&` invocation operator to execute the code now!
&$MoveToTest
}
This is similar to how the function keyword works - it simply associates the scriptblock with a function name instead of a variable - and of course you could define a function as well:
function Move-TestJpgToTestFolder {
Move-Item -Path "C:\Test.jpg" -Destination "C:\Test Folder" -force -ea 0
}
# Code
If (Test-Path C:\*.mp4) {
# We no longer need the `&` invocation operator to execute the code, PowerShell will look up the function name automatically!
Move-TestJpgToTestFolder
}
Related
I want to move all images in a directory, including subdirectories, to a new location while maintaining the existing folder structure.
Following the example, here, I put the objects into a variable, like so:
$picMetadata = Get-FileMetaData -folder (Get-childitem K:\myImages -Recurse -Directory).FullName
The move must be based on the results of a logical expression, such as the following for example.
foreach ($test01 in $picMetadata) {
if ($test01.Height -match "^[0-9]?[0-9] ") {
Write-Host "Test01.Height:" $test01.Height
}
}
Still at an early testing phase So far, I'm having no success even testing for the desired files. In the example above, I thought this simple regex test might provide for anything from "1 pixels" to "99 pixels", which would at least slim down my pictures collection (e.g. an expression without the caret, like "[0-9][0-9] " will return "NN pixels" as well as "NNN Pixels", "NNNNNN pixels", etc.)
Once I figure out how to find my desired images based on a logical, image object dimensions test, I will then need to create a script to move the files. Robocopy /MOV would be nice, but i'm probably in over my head already.
I was going to try to base it on this example (which was provided to a User attempting to COPY (not move / copy/delete) *.extension files). Unfortunately, such a simple operation will not benefit me, as I wish to move .jpg,.png,.gif, etc, based on dimensions not file extension:
$sourceDir = 'K:\myImages\'
$targetDir = ' K:\myImages_psMoveTest\'
Get-ChildItem $sourceDir -filter "*" -recurse | `
foreach{
$targetFile = $targetDir + $_.FullName.SubString($sourceDir.Length);
New-Item -ItemType File -Path $targetFile -Force;
Copy-Item $_.FullName -destination $targetFile
}
Perhaps you have a powershell script that could be used for my intended purpose? I'm just trying to move smaller images out of my collection, without having to overwrite same name images, and lose folder structure, etc.
Thank you very much for reading, and any advisory!
(Edit: Never opposed to improving Powershell skill, if you are aware of a freeware software which would perform this operation, please advise.)
If I understand your question correctly, you want to move image files with a pixel height of 1 up to 99 pixels to a new destination folder, while leaving the subfolder structure intact.
If that is true, you can do:
# needed to use System.Drawing.Image
Add-Type -AssemblyName System.Drawing
$sourceDir = 'K:\myImages'
$targetDir = 'K:\myImages_psMoveTest'
Get-ChildItem $sourceDir -File -Recurse | ForEach-Object {
$file = $_.FullName # need this for when we hit the catch block
try {
# Open image file to determine the pixelheight
$img = [System.Drawing.Image]::FromFile($_.FullName)
$height = $img.Height
# dispose of the image to remove the reference to the file
$img.Dispose()
$img = $null
if ($height -ge 1 -and $height -le 99) {
$targetFolder = Join-Path -Path $targetDir -ChildPath $_.DirectoryName.Substring($sourceDir.Length)
# create the target (sub) folder if it does not already exist
$null = New-Item -Path $targetFolder -ItemType Directory -Force
# next move the file
$_ | Move-Item -Destination $targetFolder -ErrorAction Stop
}
}
catch {
Write-Warning "Error moving file '$file': $($_.Exception.Message)"
}
}
Sorry if this question is already been answered, I could find similar questions but not the exact one I need to ask.
Let's take two examples:
1. Get-Process -name msedge,putty | Stop-Process
2. Get-Process -name msedge,putty | Foreach-Object {Stop-Process $_}
Both are doing the same operation. What about the methods used in each one? Are they the same in the sense that the first example just omits the Foreach-Object construction for the sake of code readability/aesthetics?
The first example requires the Cmdlet to support binding of the relevant parameters via the pipeline. In your case Stop-Process will bind the Process object from the pipeline to it's -InputObject parameter.
You can check that using get-help stop-process -Parameter * and see which parameters have "Accept pipeline input?" set to true.
In case a Cmdlet does not support the binding of the relevant parameters values you can wrap ForEach-Object around it, like you did in the second example. This way you use the automatic variable $_ to bind the current pipeline object (or information that you derive from it) "manually" to the corresponding parameter.
What approach should you use if a Cmdlet supports the binding of parameter values from the pipeline? That unfortunately depends. It is possible to write a Cmdlet that behaves differently, depending on how the parameter values are bound. Let me illustrate this point:
function Test-BindingFoo {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline)]
[string[]]
$InputParameter
)
begin {
Write-Host "[BEGIN]"
}
process {
foreach ($value in $InputParameter) {
Write-Host "The current value is: $value"
}
}
end {
Write-Host "[END]"
}
}
If you execute this Cmdlet using the pipeline binding the Begin block of the function is executed exactly once:
❯ "foo1", "foo2" | Test-BindingFoo
[BEGIN]
The current value is: foo1
The current value is: foo2
[END]
If you use ForEach-Object the Begin block is executed every time an object passes through the pipeline:
❯ "foo1", "foo2" | ForEach-Object { Test-BindingFoo $_ }
[BEGIN]
The current value is: foo1
[END]
[BEGIN]
The current value is: foo2
[END]
In well implemented Cmdlets the difference here should not matter. But I found it useful to be aware of what happens inside a Cmdlet when parameteres are passed in in the ways that we have discussed here.
You can do it this way too and use the kill method on the process object (operation statement):
Get-Process msedge,putty | Foreach-Object kill
# or
Get-Process msedge,putty | Foreach-Object -membername kill
I have written a script to get system variables and copy of several folders ,I wanted to create a directory for copy of several folders,to prevent duplication of folders we wanted a check condition so each time we run the script it is not creating folders. Like an example
$nfle=New-Item -ItemType "Directory" -Path "D:\Temp\" -Name "foo"
[bool]$checkfle=Test-Path "D:\Temp\foo" -PathType Any
if ( $checkfle -eq $True)
{
Write-Output "$nfle Exists"
}
else
{
$bnfle=New-Item -ItemType "Directory" -Path "D:\Temp\" -Name ("boo")
}
$cpypste=Copy-Item "D:\Temp\foo" -destination "D:\Temp\boo"
Write-Host "Succesful Copy of Folders"
So when we run the script it is creating folder foo,again when we run the script , it is displaying foo exists, and stopping the script is not going to next line, not even displaying the message.Is there a way in powershell to find out why the script is stopping or shall i add more information statements. TIA
It best to start with test-path to see if the folder is there. A "Container" is a folder/directory. Then check if you need to write the folder.
# This should allow your script to continue of error.
$ErrorActionPreference = "Continue"
# check if "C:\Temp\Foo" exist. if not make C:\Temp\foo"
$nfle = 'C:\Temp\foo'
[bool]$checkfle = Test-Path $nfle -PathType Container
if ( $checkfle -eq $True)
{
Write-Output "$nfle Exists"
}
else
{
New-Item -ItemType "Directory" -Path "C:\Temp\" -Name "foo"
}
# check if "C:\Temp\boo" exist. if not make C:\Temp\boo"
$BooFilePath = "C:\Temp\boo"
[bool]$checkboo = Test-Path $BooFilePath -PathType Container
if ( $checkboo -eq $True)
{
Write-Output " $BooFilePath Exists"
}
else
{
New-Item -ItemType "Directory" -Path "C:\Temp\" -Name "boo"
}
# This makes the folder C:\Temp\boo\foo.
# $cpypste = Copy-Item -Path "C:\Temp\foo\" -destination "C:\Temp\boo\"
# If you want copy the contents of foo into boo you will need * or -recurse
$cpypste = Copy-Item -Path "C:\Temp\foo\*" -destination "C:\Temp\boo\" -PassThru
Write-Host "Succesful Copy of Folders"
$cpypste.FullName
I have tried the demo provided and it works from my side, multiple times, so I was not able to re-create the problem.
If you would like to debug scripts in PowwerShell, you may follow this link:
https://learn.microsoft.com/en-us/powershell/scripting/components/ise/how-to-debug-scripts-in-windows-powershell-ise?view=powershell-6
I am not sure, why you are storing the result of Copy-Item into a variable, as it is null?
Hope it helps!
I got this script off the technet website, but I get an error when I try to execute it on my Windows 7 machine. I am completely new to scripting, but I wonder if this was made for an older OS and needs a bit of changing for Windows 7? I'm quite sure the guy who wrote it up tested it.
I get the Windows Script Host Error as follows:
Line: 1
Char: 10
Error: Expected Identifier
Code: 800A03F2
Source: Microsoft VBScript compilation error.
Here's the script:
Function New-BackUpFolder($destinationFolder)
{
$dte = get-date
$dte = $dte.tostring() -replace "[:\s/]", "."
$backUpPath = "$destinationFolder" + $dte
$null = New-Item -path $backUpPath -itemType directory
New-Backup $dataFolder $backUpPath $backUpInterval
} #end New-BackUpFolder
Function New-Backup($dataFolder,$backUpPath,$backUpInterval)
{
"backing up $dataFolder... check $backUppath for your files"
Get-Childitem -path $dataFolder -recurse |
Where-Object { $_.LastWriteTime -ge (get-date).addDays(-$backUpInterval) } |
Foreach-Object { copy-item -path $_.FullName -destination $backUpPath -force }
} #end New-BackUp
# *** entry point to script ***
$backUpInterval = 1
$dataFolder = "C:\fso"
$destinationFolder = "C:\BU\"
New-BackupFolder $destinationFolder
that's actually Powershell and not VB script. You need to run the code inside Powershell for this to work.
This link looks pretty good for a brief introduction if you haven't done PS before.
http://www.abstrys.com/files/BeginningPowershellScripting.html
I have a function that lets me write the file-path of files to a text file, depending on your input. That sounds confusing, but I don't know of a better way to put it, so here's the function:
Function writeAllPaths([string]$fromFolder,[string]$filter,[string]$printfile) {
Get-ChildItem -Path $fromFolder -Recurse $filter | Select-Object -Property FullName > $printfile
}
First argument being the folder you start your search from.
Second argument, the filter. *.zip for instance, will list all zip files.
Third argument, you have to provide where the text file will end up.
Sample usage: writeAllPaths c:\ *.zip c:\allZips.txt
The thing is, when I do this, Powershell won't accept commands until it's done. Which isn't very productive. Is there a way to have this run in the background when started. And preferably, give a little message when it's done. I could be opening any file that's being created in the middle of the process...
Also, I'm on Windows 7, so I'm guessing that I have Powershell 2.0
Yeah, I'm not sure about it :p
EDIT:
I used Start-Job, as suggested, as follows:
Function writeAllPaths([string]$fromFolder,[string]$filter,[string]$printfile) {
Start-Job -ScriptBlock {Get-ChildItem -Path $fromFolder -Recurse $filter | Select-Object -Property FullName > $printfile}
}
However, the file isn't created. The old function does create a file.
EDIT2: it would be preferable to have this function in my Powershell profile. This way, I can execute it whenever I want, instead of having to load in the specific ps1 file every time I boot Powershell.
More info on the Powershell profile can be found here
You can summon your own profile by typing: notepad $profile
In the new scope you create for the background job, your parameters defined for you WriteAllPaths function are not defined. Try this and you'll see they aren't:
Function writeAllPaths([string]$fromFolder,[string]$filter,[string]$printfile)
{
Start-Job { "$fromFolder, $filter, $printFile" }
}
$job = WriteAllPaths .\Downloads *.zip zips.txt
Wait-Job $job
Receive-Job $job
, ,
Try this instead:
Function writeAllPaths([string]$fromFolder, [string]$filter, [string]$printfile)
{
Start-Job {param($fromFolder,$filter,$printfile)
"$fromFolder, $filter, $printfile" } `
-ArgumentList $fromFolder,$filter,$printfile
}
$job = WriteAllPaths .\Downloads *.zip z.txt
Wait-Job $job
Receive-Job $job
.\Downloads, *.zip, z.txt
Now you see your output because we passed the parameters through to the scriptblock via -ArgumentList. What I would recommend is a function that can optionally use a background job. Just stick this function definition in your profile and you're set:
function WriteAllPaths([string]$FromFolder, [string]$Filter,
[string]$Printfile, [switch]$AsJob)
{
if (![IO.Path]::IsPathRooted($FromFolder)) {
$FromFolder = Join-Path $Pwd $FromFolder
}
if (![IO.Path]::IsPathRooted($PrintFile)) {
$PrintFile = Join-Path $Pwd $PrintFile
}
$sb = {
param($FromFolder, $Filter, $Printfile)
Get-ChildItem $FromFolder -r $filter | Select FullName > $PrintFile
}
if ($AsJob) {
Start-Job $sb -ArgumentList $FromFolder,$Filter,$PrintFile
}
else {
& $sb $FromFolder $Filter $PrintFile
}
}
Test the function (synchronously) like so:
$job = WriteAllPaths Downloads *.zip z.txt -AsJob
Wait-Job $job
Receive-Job $job
Note that I'm testing if the path is root and if not I'm prepending the current dir. I do this because the background job's starting dir is not always the same as where you executed Start-Job from.
Which version of powershell? In Powershell 2.0 I think you can use background jobs for this Start-Job.
Starts a Windows PowerShell background job.