Here is my code:
function setParameterb () {
$Name = $args[0]
"Arg1: $Name"
}
Write-Output "### Starte AlertlogRotate ###"
$folders = #('d:', 'e:', 'f:', 'g:', 'h:', 'i:', 'j:', 'k:', 'l:')
foreach ($i in $folders) {
$filenames = Get-ChildItem -Path $i\*\log -ErrorAction SilentlyContinue -Recurse -Filter alert_*.log | Select-Object FullName
}
The setParametersb function is just for test now and should only print the result. Later, I will use it to zip logfiles which get too big.
I need to get the result of this powershell object into a string to call a function for every line.
The Object looks like this:
FullName
--------
D:\AREA\log\diag\rdbms\area\area\trace\alert_area.log
D:\CONS\log\diag\rdbms\cons\cons\trace\alert_cons.log
D:\DEV01\log\diag\rdbms\dev01\dev01\trace\alert_dev01.log
G:\TEST01\LOG\diag\rdbms\test01\test01\trace\alert_test01.log
G:\TEST02\log\diag\rdbms\test02\test02\trace\alert_test02.log
I know, that I have to crop the headline "FullName", the row"--------" and some empty lines, but this is not my problem now.
My problem is to transfer the object $filenames into an array to be able to call the function setParameterb with every single line from the output.
tried lots of other stuff, but finally this solved my problem:
$filenames |ForEach-Object { setParameterb $_.FullName }
try stuff like
function setParameterb () {
param (
[Parameter(Mandatory=$true)]
$names,
)
foreach($name in $names){
"Arg1: $name"
}
}
I have a CSV file with computer names. I import them and want to use each value to do something with.
$computerNames = import-csv -Path "C:\Users\fakePath\Desktop\Programming\Powershell\remoteAddPrinters\computers.csv" -header "Computers"
function Iterate-CSV {
param (
# Parameter help description
[Parameter(Mandatory=$true)]
[array]
$csvFilePath,
[scriptblock]$functionCall #?
)
foreach ($computer in ($csvFilePath).Computers) { # Computer is the argument used in other functions.
Write-Host $functionCall.Invoke($computer)
}
}
This takes a csv file as a param then loops over it. trying to get this function passed as a parameter
function Add-NetworkPrinter {
[CmdletBinding()]
param (
# Parameter help description
[Parameter(Mandatory=$true, Position=0)]
[String]
$computerName
)
begin {
$addPrinterCue = "\\printServer\printCue"
}
process { # This is a non-terminating error and cannot be handled by try/catch
Write-Output "$addPrinterCue && $computerName"
# invoke-command -ComputerName $computerName -scriptBlock {
# # cmd.exe /c rundll32 printui.dll,PrintUIEntry /gd /n$addPrinterCue
# cmd.exe /c rundll32 printui.dll,PrintUIEntry /ga /n$addPrinterCue /q
# Restart-Service -Name Spooler -Force
# #Creates error to handle
# } -Credential $domainCredentials #-ErrorVariable errmsg 2>$null
}
end {
Write-Output "Successfully added printer to $computerName"
}
}
That way i dont have to write another foreach loop to do other tasks syncronously using values within the CSV, i can just make functions taking one argument, the iteration variable
You'll need to pass a scriptblock that calls your function.
$func = [scriptblock]{
param ($arg)
Add-NetworkPrinter $arg
}
Iterate-CSV -csvFilePath ... -functionCall $func
To reference the underlying scriptblock definition of a registered function, use the function: drive prefix to pass it as a variable:
Iterate-Csv ... -scriptBlock ${function:Add-NetworkPrinter}
Instead of creating an Advanced Function named Add-NetworkPrinter, I just created a scriptblock, as #Andrey pointed out while keeping my advanced function that iterates through values of the CSV file.
$computerNames = import-csv -Path "C:\fakePath\computers.csv" -header "Computers"
function Iterate-CSV {
param (
# Parameter help description
[Parameter(Mandatory=$true)]
[array]
$csvFilePath,
[scriptblock]$functionCall
)
foreach ($computer in ($csvFilePath).Computers) {
Write-Host $functionCall.Invoke($computer)
}
}
$addNetworkPrinter = [scriptblock]{
param ($computer)
$addPrinterCue = "\\printServer\printCue"
Write-Output "$addPrinterCue && $computer"
# invoke-command -ComputerName $computerName -scriptBlock {
# # cmd.exe /c rundll32 printui.dll,PrintUIEntry /gd /n$addPrinterCue
# cmd.exe /c rundll32 printui.dll,PrintUIEntry /ga /n$addPrinterCue /q
# Restart-Service -Name Spooler -Force
# #Creates error to handle
# } -Credential $domainCredentials #-ErrorVariable errmsg 2>$null
}
$restarComputers = [scriptblock]{
param (
[Parameter(Mandatory=$true, Position=0)]
[string]
$computer
)
Write-Output "Restarting computer $computer"
# Restart-Computer -ComputerName $computer -Credential $domainCredentials -Force
}
Then I just call my Iterate-CSV function by providing the path to csv as param1 and scriptblocks that use the same values for param2. Thanks for the ScriptBlock Info!
Iterate-CSV -csvFilePath $computerNames -functionCall $addNetworkPrinter
Iterate-CSV -csvFilePath $computerNames -functionCall $restarComputers
I have a zip file which contains several CSV files inside it. How do I read the contents of those CSV files without extracting the zip files using PowerShell?
I having been using the Read-Archive Cmdlet which is included as part of the PowerShell Community Extensions (PSCX)
This is what I have tried so far.
$path = "$env:USERPROFILE\Downloads\"
$fullpath = Join-Path $path filename.zip
Read-Archive $fullpath | Foreach-Object {
Get-Content $_.Name
}
But when I run the code, I get this error message
Get-Content : An object at the specified path filename.csv does not exist, or has been filtered by the -Include or -Exclude parameter.
However, when I run Read-Archive $fullpath, it lists all the file inside the zip file
There are multiple ways of achieving this:
1. Here's an example using Ionic.zip dll:
clear
Add-Type -Path "E:\sw\NuGet\Packages\DotNetZip.1.9.7\lib\net20\Ionic.Zip.dll"
$zip = [Ionic.Zip.ZipFile]::Read("E:\E.zip")
$file = $zip | where-object { $_.FileName -eq "XMLSchema1.xsd"}
$stream = new-object IO.MemoryStream
$file.Extract($stream)
$stream.Position = 0
$reader = New-Object IO.StreamReader($stream)
$text = $reader.ReadToEnd()
$text
$reader.Close()
$stream.Close()
$zip.Dispose()
It's picking the file by name (XMLSchema1.xsd) and extracting it into the memory stream. You then need to read the memory stream into something that you like (string in my example).
2. In Powershell 5, you could use Expand-Archive, see: https://technet.microsoft.com/en-us/library/dn841359.aspx?f=255&MSPPError=-2147217396
It would extract entire archive into a folder:
Expand-Archive "E:\E.zip" "e:\t"
Keep in mind that extracting entire archive is taking time and you will then have to cleanup the temporary files
3. And one more way to extract just 1 file:
$shell = new-object -com shell.application
$zip = $shell.NameSpace("E:\E.zip")
$file = $zip.items() | Where-Object { $_.Name -eq "XMLSchema1.xsd"}
$shell.Namespace("E:\t").copyhere($file)
4. And one more way using native means:
Add-Type -assembly "system.io.compression.filesystem"
$zip = [io.compression.zipfile]::OpenRead("e:\E.zip")
$file = $zip.Entries | where-object { $_.Name -eq "XMLSchema1.xsd"}
$stream = $file.Open()
$reader = New-Object IO.StreamReader($stream)
$text = $reader.ReadToEnd()
$text
$reader.Close()
$stream.Close()
$zip.Dispose()
Based on 4. solution of Andrey, I propose the following function:
(keep in mind that "ZipFile" class exists starting at .NET Framework 4.5)
Add-Type -assembly "System.IO.Compression.FileSystem"
function Read-FileInZip($ZipFilePath, $FilePathInZip) {
try {
if (![System.IO.File]::Exists($ZipFilePath)) {
throw "Zip file ""$ZipFilePath"" not found."
}
$Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipFilePath)
$ZipEntries = [array]($Zip.Entries | where-object {
return $_.FullName -eq $FilePathInZip
});
if (!$ZipEntries -or $ZipEntries.Length -lt 1) {
throw "File ""$FilePathInZip"" couldn't be found in zip ""$ZipFilePath""."
}
if (!$ZipEntries -or $ZipEntries.Length -gt 1) {
throw "More than one file ""$FilePathInZip"" found in zip ""$ZipFilePath""."
}
$ZipStream = $ZipEntries[0].Open()
$Reader = [System.IO.StreamReader]::new($ZipStream)
return $Reader.ReadToEnd()
}
finally {
if ($Reader) { $Reader.Dispose() }
if ($Zip) { $Zip.Dispose() }
}
}
I'm currently stuck on finding information on how to easily output the function results to a file.
$Balls = 5
$Plays = 1..26
function PowerBall {
foreach ($Trys in $Plays) {
$random = Get-Random -Input #(1..69) -Count $Balls
Write-Host $random -NoNewline
Write-Host " $Trys" -ForegroundColor Red
}
}
PowerBall | Out-File C:\Windows\Temp\numbers2.txt
Your function is not returning anything because the last line is a Write-Host command. Change your function to this:
function PowerBall {
Foreach($Trys in $Plays) {
$random = get-random -input #(1..69) -count $Balls
Write-Host $random -nonewline
write-host " $Trys" -ForegroundColor Red
"$random $Trys"
}
}
Also if you want to output as a text file then you should use Set-Content instead of Export-Csv.
So I have a script that runs at logon to search for PST's on a users machine, then copies them to a holding area waiting for migration.
When the search/copy is complete it outputs to a CSV that looks something like this:
Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied
COMP1,user1,\\comp1\c$\Test PST.pst,20.58752,08/12/2015,08/12/2015,Client copied
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,In Use
The same logon script has an IF to import the CSV if the copied status is in use and makes further attempts at copying the PST into the holding area. If it's successful it exports the results to the CSV file.
My question is, is there anyway of getting it to either amend the existing CSV changing the copy status? I can get it to add the new line to the end, but not update.
This is my 'try again' script:
# imports line of csv where PST file is found to be in use
$PST_IN_USE = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -eq "In Use" }
ForEach ( $PST_USE in $PST_IN_USE )
{ $NAME = Get-ItemProperty $PST_IN_USE.Path | select -ExpandProperty Name
$NEW_NAME = $USER + "_" + $PST_IN_USE.Size_in_MB + "_" + $NAME
# attempts to copy the file to the pst staging area then rename it.
TRY { Copy-Item $PST_IN_USE.Path "\\comp4\TEMPPST\PST\$USER" -ErrorAction SilentlyContinue
Rename-Item "\\comp4\TEMPPST\PST\$USER\$NAME" -NewName $NEW_NAME
# edits the existing csv file replacing "In Use" with "Client Copied"
$PST_IN_USE.Copied -replace "In Use","Client Copied"
} # CLOSES TRY
# silences any errors.
CATCH { }
$PST_IN_USE | Export-Csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" -NoClobber -NoTypeInformation -Append
} # CLOSES ForEach ( $PST_USE in $PST_IN_USE )
This is the resulting CSV
Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied
COMP1,user1,\\comp1\c$\Test PST.pst,20.58752,08/12/2015,08/12/2015,Client copied
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,In Use
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,Client copied
It's almost certainly something really simple, but if it is, it's something I've yet to come across in my scripting. I'm mostly working in IF / ELSE land at the moment!
If you want to change the CSV file, you have to write it completely again, not just appending new lines. In your case this means:
# Get the data
$data = Import-Csv ...
# Get the 'In Use' entries
$inUse = $data | where Copied -eq 'In Use'
foreach ($x in $inUse) {
...
$x.Copied = 'Client Copied'
}
# Write the file again
$data | Export-Csv ...
The point here is, you grab all the lines from the CSV, modify those that you process and then write the complete collection back to the file again.
I've cracked it. It's almost certainly a long winded way of doing it, but it works and is relatively clean too.
#imports line of csv where PST file is found to be in use
$PST_IN_USE = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -eq "In Use" }
$PST_IN_USE | select -ExpandProperty path | foreach {
# name of pst
$NAME = Get-ItemProperty $_ | select -ExpandProperty Name
# size of pst in MB without decimals
$SIZE = Get-ItemProperty $_ | select -ExpandProperty length | foreach { $_ / 1000000 }
# path of pst
$PATH = $_
# new name of pst when copied to the destination
$NEW_NAME = $USER + "_" + $SIZE + "_" + $NAME
TRY { Copy-Item $_ "\\comp4\TEMPPST\PST\$USER" -ErrorAction SilentlyContinue
TRY { Rename-Item "\\comp4\TEMPPST\PST\$USER\$NAME" -NewName $NEW_NAME -ErrorAction SilentlyContinue | Out-Null }
CATCH { $NEW_NAME = "Duplicate exists" }
$COPIED = "Client copied" }
CATCH { $COPIED = "In use" ; $NEW_NAME = " " }
$NEW_FILE = Test-Path "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
IF ( $NEW_FILE -eq $FALSE )
{ "Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied,New_Name" |
Set-Content "\\lccfp1\TEMPPST\PST\$HOSTNAME - $USER 4.csv" }
"$HOSTNAME,$USER,$PATH,$SIZE,$CREATION,$LASTACCESS,$COPIED,$NEW_NAME" |
Add-Content "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
} # CLOSES FOREACH #
$a = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -ne "in use" }
$b = Import-Csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
$a + $b | export-csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 8.csv" -NoClobber -NoTypeInformation
Thanks for the help. Sometimes it takes a moments break and a large cup of coffee to see things a different way.