If I have a script with some functions such as:
function FunctionOne{}
function FunctionTwo{}
How can I call them all in one line, in the same script, without having to specify the name of each function?
I'd like to do something like:
Call-AllFunctionsInCurrentScriptConsecutively #calls FunctionOne then FunctionTwo
This blog post suggests using ParseFile() for parsing the script:
$filename = 'C:\path\to\your.ps1'
[ref]$tokens = $null
[ref]$parseErrors = $null
$ast = [Management.Automation.Language.Parser]::ParseFile($filename, $tokens, $parseErrors)
Then you can invoke the functions (after you dot-sourced the script) via the call operator:
$ast.EndBlock.Statements | Where-Object { $_.Name } | ForEach-Object { & $_.Name }
For using this from within a script replace the filename with $MyInvocation.MyCommand.Path.
Parsing the file and grabbing the function names from the AST is probably the most reliable option.
A more low-tech approach would be to simply diff the function list before and after sourcing the script:
$InitialFunctions = Get-ChildItem function: -Name
. C:\path\to\script.ps1
Get-ChildItem function: -Name |Where-Object {$InitialFunctions -notcontains $_} |ForEach-Object {
& $_
}
Related
I am trying to create a reusable log function for a project I am working on in PowerShell. I am a novice with PowerShell so I am having problems figuring out why my function is not producing expected results. I am attempting to create a function that send NTFS/ACL details to a log file. This function will be incorporated in to a larger script that will change some NTFS/ACL/ACE folder rights. I have excluded some of the other code for simplification (changing rights).
Below is my current stripped down code. When it runs, it creates the log file but the file is empty. If I move the line of code that creates the log inside the log function, it creates the log file with data but it is not formatted correctly - it writes the heading (object attribute names) on one line, then the data, then then a new line with the heading, then the data. I want it to write the heading, then a line of data, line of data, .... Before I created a function for this, it worked as expected. I am a novice at PowerShell so I may not understand how to pass info in and out of the function. My Code:
#variables
$rootDir = "\\server1\share1"
$logDir = "c:\temp"
$date = (Get-Date -Format "MMddyyyy-HHmm")
$logData =#()
#My Logging Function
Function CreateLog {
#create and object to store attributes
$logObj = New-Object -TypeName psobject -Property #{
FolderPath = $Folder.Fullname
IdentityReference = $ACL.IdentityReference.ToString()
folder = $Folder[0]
FileSystemRights = $ACL.FileSystemRights.ToString()
InheritanceFlags = $ACL.InheritanceFlags.ToString()
}
$Folders=(Get-ChildItem -Path $rootDir -Directory -Recurse)
foreach ($Folder in $Folders){
$ACLs = get-acl $Folder.fullname | ForEach-Object {$_.Access}
Foreach ($ACL in $ACLs){
CreateLog
if($ACL.FileSystemRights -eq "FullControl"){
Write-Host "DO SOMETHING"
}
}
$logData | select folder,IdentityReference,FileSystemRights,InheritanceFlags,FolderPath | Format-Table -Wrap | Out-File $logDir\testlog-$date.log -Append
I assume you're looking for something like this:
#variables
$rootDir = "\\server1\share1"
$logDir = "c:\temp"
$date = Get-Date -Format "MMddyyyy-HHmm"
#My Logging Function
Function CreateLog {
param(
[Parameter(Mandatory)]
[System.Security.AccessControl.FileSystemAccessRule] $AccessRule,
[Parameter(Mandatory)]
[string] $Folder
)
[pscustomobject]#{
FolderPath = $Folder
IdentityReference = $AccessRule.IdentityReference
FileSystemRights = $AccessRule.FileSystemRights
InheritanceFlags = $AccessRule.InheritanceFlags
}
}
Get-ChildItem -Path $rootDir -Directory -Recurse | ForEach-Object {
foreach($rule in ($_ | Get-Acl).Access) {
CreateLog -AccessRule $rule -Folder $_.FullName
if($rule.FileSystemRights.HasFlag([Security.AccessControl.FileSystemRights]::FullControl)) {
# Do something here
}
}
} | Export-Csv $logDir\testlog-$date.log -NoTypeInformation
Your current code has a few syntax errors (missing }), your function seem to be assigning the objects to $logObj but then $logObj is never outputted from it, hence producing no output.
Your $logData array is defined but never used, leaving aside is not needed at all in this case. Your function should, ideally, take the arguments for the Access Rules and Path, see Functions with Parameters to learn more.
Format-Table as well as the other Format-* cmdlets should be primarily used for console display, should not be used for exporting data. In this case you should use Export-Csv, this way your data preserves its structure and can be imported back at ease, be filtered and be sorted.
I am fairly new to Powershell. I am attempting to re-write the names of GPO backup folders to use their friendly name rather than their GUID by referencing the name in each GPO backup's 'gpresult.xml' file that is created as part of the backup. However, I do not understand how I can reference the specific object (in this case, the folder name) that is being read into the ForEach-Object loop in order to read into the file beneath this folder.
function Backup_GPO {
$stamp = Get-Date -UFormat "%m%d"
New-Item -ItemType "directory" -Name $stamp -Path \\dc-16\share\GPO_Backups -Force | out-null # create new folder to specify day backup is made
Backup-GPO -All -Path ("\\dc-16\share\GPO_Backups\" + "$stamp\" )
Get-ChildItem -Path \\dc-16\share\GPO_Backups\$stamp | ForEach-Object {
# I want to reference the current folder here
[xml]$file = Get-Content -Path (folder that is being referenced in for loop)\gpresult.xml
$name = $file.GPO.Name
}
I'm coming from Python, where if I want to reference the object I'm currently iterating on, I can do so very simply -
for object in list:
print(object)
How do you reference the currently in-use object in Powershell's ForEach-Object command?
I'm coming from Python, where if I want to reference the object I'm currently
iterating on, I can do so very simply -
for object in list:
print(object)
The direct equivalent of that in PowerShell is the foreach statement (loop):
$list = 1..3 # create a 3-element array with elements 1, 2, 3
foreach ($object in $list) {
$object # expression output is *implicitly* output
}
Note that you cannot directly use a foreach statement in a PowerShell pipeline.
In a pipeline, you must use the ForEach-Object cmdlet instead, which - somewhat confusingly - can also be referred to as foreach, via an alias - it it is only the parsing mode that distinguishes between the statement and the cmdlet's alias.
You're using the ForEach-Object cmdlet in the pipeline, where different rules apply.
Script blocks ({ ... }) passed to pipeline-processing cmdlets such as ForEach-Object and Where-Object do not have an explicit iteration variable the way that the foreach statement provides.
Instead, by convention, such script blocks see the current pipeline input object as automatic variable $_ - or, more verbosely, as $PSItem.
While the foreach statement and the ForEach-Object cmdlet operate the same on a certain level of abstraction, there's a fundamental difference:
The foreach statement operates on collections collected up front in memory, in full.
The ForEach-Object cmdlet operates on streaming input, object by object, as each object is being received via the pipeline.
This difference amounts to the following trade-off:
Use the foreach statement for better performance, at the expense of memory usage.
Use the ForEach-Object cmdlet for constant memory use and possibly also for the syntactic elegance of a single pipeline, at the expense of performance - however, for very large input sets, this may be the only option (assuming you don't also collect a very large dataset in memory on output).
Inside the ForEach-Object scriptblock, the current item being iterated over is copied to $_:
Get-ChildItem -Filter gpresult.xml |ForEach-Object {
# `$_` is a FileInfo object, `$_.FullName` holds the absolute file system path
[xml]$file = Get-Content -LiteralPath $_.FullName
}
If you want to specify a custom name, you can either specify a -PipelineVariable name:
Get-ChildItem -Filter gpresult.xml -PipelineVariable fileinfo |ForEach-Object {
# `$fileinfo` is now a FileInfo object, `$fileinfo.FullName` holds the absolute file system path
[xml]$file = Get-Content -LiteralPath $fileinfo.FullName
}
or use a foreach loop statement, much like for object in list in python:
foreach($object in Get-ChildItem -Filter gpresult.xml)
{
[xml]$file = Get-Content -LiteralPath $object.FullName
}
Another way...
$dlist = Get-ChildItem -Path "\\dc-16\share\GPO_Backups\$stamp"
foreach ($dir in $dlist) {
# I want to reference the current folder here
[xml]$file = Get-Content -Path (Join-Path -Path $_.FullName -ChildPath 'gpresult.xml')
$name = $file.GPO.Name
}
Here's my solution. It's annoying that $_ doesn't have the full path. $gpath is easier to work with than $_.fullname for joining the two strings together on the next line with get-content. I get a gpreport.xml file when I try backup-gpo. Apparently you can't use relative paths like .\gpo_backups\ with backup-gpo.
mkdir c:\users\js\gpo_backups\
get-gpo -all | where displayname -like '*mygpo*' |
backup-gpo -path c:\users\js\gpo_backups\
Get-ChildItem -Path .\GPO_Backups\ | ForEach-Object {
$gpath = $_.fullname
[xml]$file = Get-Content -Path "$gpath\gpreport.xml"
$file.GPO.Name
}
I've created a proxy function for Remove-Item, which deletes to the recycle bin instead of permanently (using the proxy so that I can seamlessly replace the rm alias, without breaking 3rd party scripts).
However, it doesn't work when a file is piped into the function. The heart of the proxy function is this:
if ($PSBoundParameters['DeletePermanently'] -or $PSBoundParameters['LiteralPath'] -or $PSBoundParameters['Filter'] -or $PSBoundParameters['Include'] -or $PSBoundParameters['Exclude'] -or $PSBoundParameters['Recurse'] -or $PSBoundParameters['Force'] -or $PSBoundParameters['Credential']) {
if ($PSBoundParameters['DeletePermanently']) { $PSBoundParameters.Remove('DeletePermanently') | Out-Null }
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
} else {
$scriptCmd = {& Recycle-Item -Path $PSBoundParameters['Path'] }
}
So, my custom Recycle-Item function is only called if Path is the only parameter. So, something like Get-ChildItem .\temp\ | rm -DeletePermanently works just fine, but Get-ChildItem .\temp\ | rm has an error because the Path passed to Recycle-Item is $null.
I've tried passing $Path instead of $PSBoundParameters['Path'] and tried splatting #PSBoundParameters like the call to $wrappedCmd above, but none of it appears to do much good. I've copied the params from this function to Recycle-Item, to ensure that it is expecting input from the pipeline, but that doesn't seem to help either. Some of those changes appear to pass along the file name, but not the full path, so I don't know if there's some magic inside Remove-Item that I need to replicate to handle a file object from the pipeline.
Recycle-Item is just a basic function:
function Recycle-Item($Path) {
$item = Get-Item $Path
$directoryPath = Split-Path $item -Parent
$shell = new-object -comobject "Shell.Application"
$shellFolder = $shell.Namespace($directoryPath)
$shellItem = $shellFolder.ParseName($item.Name)
$shellItem.InvokeVerb("delete")
}
As mentioned in the comments, the provider cmdlets usually bind on LiteralPath when you pipe objects between them. This way allows Path to support wildcard globbing without the chance of passing ambiguous item paths between cmdlets.
Remove-Item has only two parameter sets, and they are named after their mandatory parameters, Path and LiteralPath
To solve your problem, simply check for all defined parameters that are not one of these two, then pass the appropriate value to Remove-Item based on the $PSCmdlet.ParameterSetName value:
if(#($PSBoundParameters.Keys |Where-Object {#('DeletePermanently','Filter','Include','Exclude','Recurse','Force','Credential') -contains $_}).Count -ge 1){
# a parameter other than the Path/LiteralPath or the common parameters was specified, default to Remove-Item
if ($PSBoundParameters['DeletePermanently']) {
$PSBoundParameters.Remove('DeletePermanently') | Out-Null
}
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
} else {
# apart from common parameters, only Path/LiteralPath was specified, go for Recycle-Item
$scriptCmd = {& Recycle-Item -Path $PSBoundParameters[$PSCmdlet.ParameterSetName] }
}
I am trying to write a PowerShell script that will loop through a directory in C:\ drive and parse the filenames with the file extension to another script to use.
Basically, the output of the directory listing should be accessible to be parsed to another script one by one. The script is a compiling script which expects an argument (parameter) to be parsed to it in order to compile the specific module (filename).
Code:
Clear-Host $Path = "C:\SandBox\"
Get-ChildItem $Path -recurse -force | ForEach { If ($_.extension -eq ".cob")
{
Write-Host $_.fullname
}
}
If ($_.extension -eq ".pco")
{
Write-Host $_.fullname }
}
You don't need to parse the output as text, that's deprecated.
Here's something that might work for you:
# getmyfiles.ps1
Param( [string])$Path = Get-Location )
dir $Path -Recurse -Force | where {
$_.Extension -in #('.cob', '.pco')
}
# this is another script that calls the above
. getmyfile.ps1 -Path c:\sandbox | foreach-object {
# $_ is a file object. I'm just printing its full path but u can do other stuff eith it
Write-host $_.Fullname
}
Clear-Host
$Path = "C:\Sandbox\"
$Items = Get-ChildItem $Path -recurse -Include "*.cob", "*.pco"
From your garbled code am guessing you want to return a list of files that have .cob and .pco file extensions. You could use the above code to gather those.
$File = $Items.name
$FullName = $items.fullname
Write-Host $Items.name
$File
$FullName
Adding the above lines will allow you to display them in various ways. You can pick the one that suites your needs then loop through them on a for-each.
As a rule its not a place for code to be writen for you, but you have tried to add some to the questions so I've taken a look. Sometimes you just want a nudge in the right direction.
I have a function that will change the filename and move it to a specific computer. The function is duplicated for another computer, so I now have two specific functions for each filename. I can't seem to figure out how to get the script to choose the function I want it to use. Here is what I've tried with no luck:
$files = dir -Path \\server\PPTV\*\*.* -Include atrium.*, clirel.* -Recurse {
(If ($file.FullName -eq "atrium.*" | Rename-Copy-Atrium),
(If ($file.FullName -eq "clirel.*" | Rename-Copy-Clirel)))
}
How can I get this to pipe to the function and run? Rename-Copy-Atrium for example is one of the function names.
The proper syntax for an if() statement in powershell is:
if([condition])
{
[execute this code]
}
So you would want to do something like:
$files = dir -Path \\server\PPTV\*\*.* -Include atrium.*, clirel.* -Recurse
foreach($file in $files)
{
if($file.FullName -like "atrium.*")
{
# I assume the function takes a fileinfo object as a parameter argument
Rename-Copy-Atrium $file
}
elseif($file.FullName -like "clirel.*")
{
Rename-Copy-Clirel $file
}
}