I'm trying to output a CSV in the current directory, but in a folder that matches the $ComputerName, which already exists. I'm doing this for a list of machines regularly and rather than manually put them in their folder it would be awesome to do it in the script.
Here is the current code, writing to the script directory.
#writing file to
[Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath
Write-Host ("Saving CSV Files at " + [Environment]::CurrentDirectory + " Named the following.")
Write-Host $PritnersFilename
I've tried adding $ComputerName to various locations and had no luck.
Examples:
Write-Host ("Saving CSV Files at " + [Environment]::CurrentDirectory\$ComputerName + " Named the following.")
Write-Host ("Saving CSV Files at " + [Environment]::(CurrentDirectory\$ComputerName) + " Named the following.")
EDIT: $ComputerName is the variable of the target, not the local host
It would be easier if I saw the whole code. But I made up an example because I felt it would be easier to explain it likes this since I don't know where you get your variables from.
Pretty straight forward, it loops through the computers and if there isn't a folder in current folder named $computername it creates one. Then your export code comes in where it exports computer data to that folder we just created.
Key part: Using ".\" is the same thing as current folder.
cd C:\Scriptfolder\
# computer variables for example
$computers = #()
$computers += "HOST1"
$computers += "HOST2"
$computers += "HOST3"
# looping through all objects
Foreach($computer in $computers){
# creating folder named after computername if one doesn't exist
if(!(Test-Path ".\$computer")){
New-Item -ItemType Directory -Name $computer -Path ".\"
}
# output path with computername in it
$outputpath = ".\$computer\output.csv"
# your export code
$computer | Export-CSV $outputpath
}
[Environment]::CurrentDirectory\$ComputerName, due to being inside (...) and being used as an operand of the + operator, is parsed in expression mode, which causes a syntax error.
For an overview of PowerShell's parsing modes, see this answer of mine.
You need "..." (an expandable string) to perform your string concatenation, using subexpression operator $(...) to embed expression [Environment]::CurrentDirectory and embedding a reference to variable $ComputerName directly.
"$([Environment]::CurrentDirectory)\$ComputerName"
For an overview of string expansion (string interpolation) in PowerShell,
see this answer of mine.
Alternatively, you could use an expression with + as well (or even mix the two approaches):
# Enclose the whole expression in (...) if you want to use it as a command argument.
[Environment]::CurrentDirectory + '\' + $ComputerName
Note: The most robust (albeit slower) method for building filesystem paths is to use the Join-Path cmdlet:
# Enclose the whole command in (...) to use it as part of an expression.
Join-Path ([Environment]::CurrentDirectory) $ComputerName
Note the need for (...) around [Environment]::CurrentDirectory to ensure that it is recognized as an expression. Otherwise, since the command is parsed in argument mode, [Environment]::CurrentDirectory would be treated as a literal.
Related
I have been given the task to write a PS script that will, from a list of machines in a text file:
Output the IP address of the machine
Get the version of the SCCM client on the machine
Produce a GPResult HTMl file
OR
Indicate that the machine is offline
With a final stipulation of running the script in the background (Job)
I have the scriptblock that will do all of these things, and even have the output formatted like I want. What I cannot seem to do, is get the scriptblock to call the source file from within the same directory as the script. I realize that I could simply hard-code the directories, but I want to be able to run this on any machine, in any directory, as I will need to use the script in multiple locations.
Any suggestions?
Code is as follows (Note: I am in the middle of trying stuff I gathered from other articles, so it has a fragment or two in it [most recent attempt was to specify working directory], but the core code is still there. I also had the idea to declare the scriptblock first, like you do with variables in other programming languages, but more for readability than anything else):
# List of commands to process in job
$ScrptBlk = {
param($wrkngdir)
Get-Content Hostnames.txt | ForEach-Object {
# Check to see if Host is online
IF ( Test-Connection $_ -count 1 -Quiet) {
# Get IP address, extracting only IP value
$addr = (test-connection $_ -count 1).IPV4Address
# Get SCCM version
$sccm = (Get-WmiObject -NameSpace Root\CCM -Class Sms_Client).ClientVersion
# Generate GPResult HTML file
Get-GPResultantSetOfPolicy -computer $_.name -reporttype HTML -path ".\GPRes\$_ GPResults.html"}
ELSE {
$addr = "Offline"
$sccm = " "}
$tbl = New-Object psobject -Property #{
Computername = $_
IPV4Address = $addr
SCCM_Version = $sccm}}}
# Create (or clear) output file
Echo "" > OnlineCheckResults.txt
# Create subdirectory, if it does not exist
IF (-Not (Get-Item .\GPRes)) { New-Item -ItemType dir ".\GPRes" }
# Get current working directory
$wrkngdir = $PSScriptRoot
# Execute script
Start-Job -name "OnlineCheck" -ScriptBlock $ScrptBlk -ArgumentList $wrkngdir
# Let job run
Wait-Job OnlineCheck
# Get results of job
$results = Receive-Job OnlineCheck
# Output results to file
$results >> OnlineCheckResults.txt | FT Computername,IPV4Address,SCCM_Version
I appreciate any help you may have to offer.
Cheers.
~DavidM~
EDIT
Thanks for all the help. Setting the working directory works, but I am now getting a new error. It has no line reference, so I am not sure where the problem might be. New code below. I have moved the sriptblock to the bottom, so it is separate from the rest of the code. I thought that might be a bit tidier. I do apologize for my earlier code formatting. I will attempt to do better with the new example.
# Store working directory
$getwkdir = $PWD.Path
# Create (or clear) output file
Write-Output "" > OnlineCheckResults.txt
# Create subdirectory, if it does not exist. Delete and recreate if it does
IF (Get-Item .\GPRes) {
Remove-Item -ItemType dir "GPRes"
New-Item -ItemType dir "GPRes"}
ELSE{
New-Item -ItemType dir "GPRes"}
# Start the job
Start-Job -name "OnlineCheck" -ScriptBlock $ScrptBlk -ArgumentList $getwkdir
# Let job run
Wait-Job OnlineCheck
# Get results of job
$results = Receive-Job OnlineCheck
# Output results to file
$results >> OnlineCheckResults.txt | FT Computername,IPV4Address,SCCM_Version
$ScrptBlk = {
param($wrkngdir)
Set-Location $wrkngdir
Get-Content Hostnames.txt | ForEach-Object {
IF ( Test-Connection $_ -count 1 -Quiet) {
# Get IP address, extracting only IP value
$addr = (test-connection $_ -count 1).IPV4Address
# Get SCCM version
$sccm = (Get-WmiObject -NameSpace Root\CCM -Class Sms_Client).ClientVersion
Get-GPResultantSetOfPolicy -computer $_.name -reporttype HTML -path ".\GPRes\$_ GPResults.html"}
ELSE {
$addr = "Offline"
$sccm = " "}
$tbl = New-Object psobject -Property #{
Computername = $_
IPV4Address = $addr
SCCM_Version = $sccm}}}
Error text:
Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that
is not null or empty, and then try the command again.
+ CategoryInfo : InvalidData: (:) [Test-Connection], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand
+ PSComputerName : localhost
As Theo observes, you're on the right track by trying to pass the desired working directory to the script block via -ArgumentList $wrkngdir, but you're then not using that argument inside your script block.
All it takes is to use Set-Location at the start of your script block to switch to the working directory that was passed:
$ScrptBlk = {
param($wrkngdir)
# Change to the specified working dir.
Set-Location $wrkngdir
# ... Get-Content Hostnames.txt | ...
}
# Start the job and pass the directory in which this script is located as the working dir.
Start-Job -name "OnlineCheck" -ScriptBlock $ScrptBlk -ArgumentList $PSScriptRoot
In PSv3+, you can simplify the solution by using the $using: scope, which allows you to reference variables in the caller's scope directly; here's a simplified example, which you can run directly from the prompt (I'm using $PWD as the desired working dir., because $PSScriptRoot isn't defined at the prompt (in the global scope)):
Start-Job -ScriptBlock { Set-Location $using:PWD; Get-Location } |
Receive-Job -Wait -AutoRemove
If you invoke the above command from, say, C:\tmp, the output will reflect that path too, proving that the background job ran in the same working directory as the caller.
Working directories in PowerShell background jobs:
Before PowerShell 7.0, starting background jobs with Start-Job uses the directory returned by [environment]::GetFolderPath('MyDocuments') as the initial working directory, which on Windows is typically $HOME\Documents, whereas it is just $HOME on Unix-like platforms (in PowerShell Core).
Setting the working dir. for the background job via Start-Job's -InitializationScript script-block argument via a $using: reference - e.g., Start-Job -InitializationScript { $using:PWD } { ... } should work, but doesn't in Windows PowerShell v5.1 / PowerShell [Core] 6.x, due to a bug (the bug is still present in PowerShell 7.0, but there you can use -WorkingDirectory).
In PowerShell (Core) 7+, Start-Job now sensibly defaults to the caller's working directory and also supports a -WorkingDirectory parameter to simplify specifying a working directory.
In PowerShell (Core) 6+ you can alternatively start background jobs with a post-positional & - the same way that POSIX-like shells such as bash do - in which case the caller's working directory is inherited; e.g.:
# PS Core only:
# Outputs the caller's working dir., proving that the background job
# inherited the caller's working dir.
(Get-Location &) | Receive-Job -Wait -AutoRemove
If I understand correctly, I think that the issue you are having is because the working directory path is different inside the execution of the Script Block. This commonly happens when you execute scripts from Scheduled tasks or pass scripts to powershell.exe
To prove this, let's do a simple PowerShell code:
#Change current directory to the root of C: illustrate what's going on
cd C:\
Get-Location
Path
----
C:\
#Execute Script Block
$ScriptBlock = { Get-Location }
$Job = Start-Job -ScriptBlock $ScriptBlock
Receive-Job $Job
Path
----
C:\Users\HAL9256\Documents
As you can see the current path inside the execution of the script block is different than where you executed it. I have also seen inside of Scheduled tasks, paths like C:\Windows\System32 .
Since you are trying to reference everything by relative paths inside the script block, it won't find anything. One solution is to use the passed parameter to change your working directory to something known first.
Also, I would use $PWD.Path to get the current working directory instead of $PSScriptRoot as $PSScriptRoot is empty if you run the code from the console.
I have a few hundred folders to represent all my subnets.
Example, for the folder name
172.31.3.250_29
represents the subnet
172.31.3.250/29
Because you can't have "/" in windows folder names.
I have about 250 of these folders.
I am writing a powershell script that will take each folder name and run a script against the "subnet" that the folder represents, and dump a log in that folder.
However, I'm stuck on a simple issue.
I have successfully been able to extract the name of all sub folders in a folder
I need to replace the "_" with "/" to denote proper subnet format for my script.
$NmapFolder = Get-ChildItem -Path "U:\nmap reports\Nmap Subnet Scans\August2019" -Recurse -Directory -Force -ErrorAction SilentlyContinue
$data = ForEach ($items in $NmapFolder){
$items = $items.replace('_','/')
#eventually will have nmap script logic
#eventually will have "wait for nmap done" check before proceeding with next folder/subnet
write-host $items
}
Which produces the error...
Method invocation failed because [System.IO.DirectoryInfo] does not contain a method named 'replace'.
At line:6 char:1
+ $items = $items.replace('_','/')
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Get-ChildItem returns a System.IO.DirectoryInfo or System.IO.FileInfo object, not a string. Since you specified -Directory, you will first need to convert the DirectoryInfo objects to strings
One easy way to do this would be to change the first line of your code:
$NmapFolder = #( ( Get-ChildItem -Path "U:\nmap reports\Nmap Subnet Scans\August2019" -Recurse -Directory -Force -ErrorAction SilentlyContinue ).Name )
This will roll up the Name property of every returned DirectoryInfo object into an array of strings, which you can then iterate over. However, if you have a deeper directory structure to traverse (I assume you might since you are using -Recurse), you may want to consider making use of piping your Get-ChildItem command into Resolve-Path -Relative, and parsing your subnet folder name from that array of strings, as it looks like eventually you may try writing something back into that folder from this script.
I am trying to make a powershell script (5.1) that will copy several files and folders from several hosts, these hosts change frequently therefore it would be ideal if I can use a list that I can append when required.
I have this all working using xcopy so I know the locations exist. I want to ensure that if a change is made when I am not In work someone can just add or remove a host in the text file and the back up will continue to work.
The code I have is supposed to go through each host in my list of hosts and copy all the files from the list of file paths before moving onto the next host.
But there are 2 errors showing up:
The term '\REMOTEHOST\c$\Users\Public\desktop\back-up\$Computers' is not recognized as the name of a cmdlet, function, script
file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:8 char:17
and:
copy-item : Cannot find path '\HOST\C$\LK\Appdata\Cmmcfg C$\LKAppData\Errc C$\LK\Appdata\TCOMP C$\LK\Probes C$\LK\Appdata\CAMIO C$\LK\Appdata\LaunchPad C$\LK\Appdata\Wincmes
C$\barlen.dta C$\Caliprogs C$\Cali' because it does not exist.
This does not seem to reading through the list as I intended, I have also noticed that the HOST it is reading from is 6th in the list and not first.
REM*This file contains the list of hosts you want to copy files from*
$computers = Get-Content 'Y:\***FILEPATH***\HOSTFILE.txt'
REM*This is the file/folder(s) you want to copy from the hosts in the $computer variable*
$source = Get-Content 'Y:\***FILEPATH***\FilePaths.txt'
REM*The destination location you want the file/folder(s) to be copied to*
$destination = \\**REMOTEHOST**\c$\Users\Public\desktop\back-up\$Computers
foreach ($item in $computers) {
}
foreach ($item in $source) {
}
copy-item \\$computer\$source -Destination $destination -Verbose
Your destination variable needs to be enclosed in quotes. To have it evaluate other variables inside of it, enclose it in double quotes. Otherwise PowerShell thinks it's a command you are trying to run.
$destination = "\\**REMOTEHOST**\c$\Users\Public\desktop\back-up\$Computers"
cracked it, thank you for your help. I was messing up the foreach command!I had both variables set to Item, so I was confusing things!
foreach ($itemhost in $computers) {
$destination = "\Remotehost\c$\Users\xoliver.jeffries\desktop\back-up\$itemhost"
foreach ($item in $source)
{copy-item "\$itemhost\$item*" -Destination $destination -Verbose -recurse}
}
Its not the neatest output but that's just a snag! the code now enables me to use a list of hosts and a list files and copy them to a remote server!
So I'm trying to write a powershell script that will go through a folder full of .evtx files, send out each one via syslog, then append ".done" to the filename of the .evtx file after doing so.
The thing is, I'm not quite sure how to reference the current log file I am on within the Foreach-Object loop.
Hopefully the following code will explain my dillema.
# begin foreach loop
Get-ChildItem $evtxfolder -Filter *.evtx | `
Foreach-Object {
$LPARGS = ("-i:evt", "-o:syslog", "SELECT STRCAT(`' evt-Time: `', TO_STRING(TimeGenerated, `'dd/MM/yyyy, hh:mm:ss`')),EventID,SourceName,ComputerName,Message INTO $SERVER FROM $CURRENTOBJECT") #obviously, this won't work.
$LOGPARSER = "C:\Program Files (x86)\Logparser 2.2\logparser.exe"
$LP = Start-Process -FilePath $LOGPARSER -ArgumentList $LPARGS -Wait -Passthru -NoNewWindow
$LP.WaitForExit() # wait for logs to finish
If you look in $LPARGS, you'll see that I put $SERVER and $CURRENTOBJECT. Obviously, the way I have it now will not work, but obviously, that won't work. So basically, I'm trying to put the variable $SERVER (passed in as a parameter) into the arguments for logparser, and reference whatever current event log it is working on to put in the "FROM" statement so that it knows to work on one .evtx file at a time. What would be the proper way to do this?
An example of the INTO FROM statement:
..snippet..
SourceName,ComputerName,Message INTO #192.168.56.30 FROM 'C:\Eventlogs\20131125.evtx'"
Of course, 'C:\Eventlogs\20131125.evtx' would change as it goes through the contents of the directory.
If $server is defined outside your script above it will be available inside your string for $LPARGS. As for the $CURRENTOBJECT, that would be $_. In this case, it will be a FileInfo object. It is likely you want the Name property e.g. $($_.Name).
I have a small Powershell script I wrote to help rename folders of files, based off the filenames in another folder.
I have two read-host lines that catch the input from the user and store the input as the source folder and destination folder as strings. This makes life easier as I can drag and drop rather than full typing the path.
The problem is that Powershell keeps throwing errors, saying it can't find the drive "X:
This seems to be being caused by the quotes around the path, as removing them after dragging and dropping works fine.
Here is how it is captured:
$source = Read-Host "Source folder"
$destination = Read-Host "Destination folder"
[array]$a = Get-ChildItem $source
[array]$b = Get-ChildItem $destination
What is the easiest way to remove the quotes from those strings, before running the Get-ChildItem command? I have tried things like $source.replace, $_. trim and also $source -replace ('"', "")
Can't seem to get this to work.
Use single quotes, like so:
PS C:\> $foo = 'x:\"some weird\path"'
PS C:\> $foo
x:\"some weird\path"
PS C:\> $foo.Replace('"', '')
x:\some weird\path
PS C:\> $foo -replace '"', ''
x:\some weird\path