So my larger project is to come up with a way of finding UNC path names that are too long, and once I have that information, use them as mapped drives so that I can run Get-Child information below that point.
I have the following code which gets me a Write of the folders that match that criteria, and display the UNC path to me, and I would like to add that information to an array that can be called back
Here is the code I have at the moment:
Get-ChildItem "\\Server\Share" -recurse -ErrorAction SilentlyContinue -ErrorVariable err
foreach ($errorRecord in $err)
{
if ($errorRecord.Exception -is [System.IO.PathTooLongException])
{
Write-Warning ($errorRecord.TargetObject)
$ErrorArray = $errorRecord
}
else
{
Write-Error -ErrorRecord $errorRecord
}
}
Out-File C:\ErrorArray.txt
Being new to PS, can anyone please point me in the right direction please?
Add a variable $results to hold errors you want to capture and pipe that out to a file. As well as that grab the string with error message - $errorRecord.TargetObject - rather than the entire error object.
$results = #()
Get-ChildItem "\\Server\Share" -recurse -ErrorAction SilentlyContinue -ErrorVariable err
foreach ($errorRecord in $err)
{
if ($errorRecord.Exception -is [System.IO.PathTooLongException])
{
Write-Warning ($errorRecord.TargetObject)
$results += ,$errorRecord.TargetObject
}
else
{
Write-Error -ErrorRecord $errorRecord
}
}
$results | Out-File C:\temp\ErrorArray.txt
Related
Need help troubleshooting an the Array and Scriptblock
OR Maybe this is better using param and functions???
Script Objective: To easily update the list of applications to be installed
Getting error below.
'
At C:\Temp\appinstall.ps1:7 char:10
$Firefox={
~
The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept
assignments, such as a variable or a property.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : InvalidLeftHandSide
'
Start-Transcript -Append c:\Deploy\log.txt
$ProgressPreference = 'SilentlyContinue';
#Change App Name, Source, MSI/EXE, Argument
$AppArray= (
$Firefox={
$App= "Firefox";
$App_source= "https://download.mozilla.org/?product=firefox-latest&os=win64&lang=en-US";
$destination = "c:\Deploy\$App.exe";
$Argument= "/S";
},
$Chrome=
{
$App= "Chrome";
$App_source= "https://dl.google.com/tag/s/defaultbrowser/edgedl/chrome/install/GoogleChromeStandaloneEnterprise64.msi";
$destination = "c:\Deploy\$App.exe";
$Argument= "/norestart","/qn";
}
)
$InstallScriptBlock=
{
$installed = (Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where { $_.DisplayName -Match "$App" });
$installed.displayname
if ($installed.displayname -Match $App) {
Write-Host "$software installed"
}else{
If ((Test-Path $destination) -eq $false) {
New-Item -ItemType File -Path $destination -Force
}
#install software
Invoke-WebRequest $App_source -OutFile $destination
Start-Process -FilePath "$destination" -ArgumentList "$Argument" -Wait
#Delete installer
Remove-Item -recurse "$destination"
}
}
ForEach ($Program in $AppArray) {Invoke-Command -ScriptBlock $InstallScriptBlock}
Stop-Transcript
It looks like you're trying to create a nested hashtable (#{ ... }), but your syntax is flawed - see the linked docs.
However:
It should suffice in your case to create an array of hashtables to iterate over with foreach
There's no need to use a separate script block ({ ... }) - just use the body of the foreach loop statement.
As an aside: While using Invoke-Command for local invocation of script blocks works, it usually isn't necessary, because &, the call operator, will do (e.g. $sb = { 'hi' }; & $sb). Invoke-Command's primary purpose is to execute a script block on a remote machine.
Generally, you can use variables as-is as command arguments, without enclosing them in "..." - even if their values contain spaces. E.g., Write-Output $foo is sufficient, no need for Write-Output "$foo"
To put it all together:
# Create an array whose elements are hashtables.
$appArray = (
#{
App = ($thisApp = 'Firefox')
App_source = 'https://download.mozilla.org/?product=firefox-latest&os=win64&lang=en-US'
Destination = "c:\Deploy\$thisApp.exe"
Argument = '/S'
},
#{
App = ($thisApp = 'Chrome')
App_source = 'https://dl.google.com/tag/s/defaultbrowser/edgedl/chrome/install/GoogleChromeStandaloneEnterprise64.msi'
Destination = "c:\Deploy\$thisApp.exe"
Argument = '/norestart /qn'
}
)
foreach ($app in $appArray) {
# Note how $app.<key> is used to refer to the entries of the hashtable at hand,
# e.g. $app.App yields "Firefox" for the first hashtable.
$installed = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object { $_.DisplayName -Match $app.App }
$installed.displayname
if ($installed.displayname -Match $app.App) {
Write-Host "$($app.App) already installed."
}
else {
if ((Test-Path $app.Destination) -eq $false) {
New-Item -ItemType File -Path $app.Destination -Force
}
#install software
Invoke-WebRequest $app.App_source -OutFile $app.Destination
Start-Process -FilePath $app.Destination -ArgumentList $app.Argument -Wait
#Delete installer
Remove-Item -Recurse $app.Destination
}
}
Note:
I've removed unnecessary ; and I've switched to using verbatim (single-quoted) strings ('...') when no string interpolation via expandable (double-quoted) strings ("...") is required, both for conceptual clarity and to avoid potentially unwanted expansions.
Note the use of aux. variable $thisApp in the App key, which allows referencing it in the later Destination key, in an expandable string ("c:\Deploy\$thisApp.exe").
GitHub suggestion #13782 looks for a more elegant way to allow hashtable entries to reference one another.
I'm working on a script to compare two directories. There are two main things I want the script to show in the output--which files exist on one directory but not the other, and which files appear in both directories but have differences in them. Matching files don't need to show up.
I got some advice before on how to achieve this, but since I'm still pretty new to PS I'm having trouble executing it. What I'm trying to do is this:
I have Path #1. For each file in that path, I want to test for their existence on Path #2.
If the file exists in both paths, do a hash comparison between them. If there are differences, add the files to List A.
If the file appears in Path 1 but not Path 2, put them in List B.
This isn't as important, but would it also be possible to find files that exist in Path 2 but not Path 1? For work purposes that probably won't matter, but it will still be nice just in case.
Take the output and format it so that it can show something like: "The following files exist in Path 1 and not Path 2," and "The following files exist in both paths but have differences."
Basically, I don't just want an info dump of files to be the output and people end up having to puzzle through it. And like I said, I think the advice I received on how to do it will be good, I'm just having trouble making it work.
Here's the code I have so far:
$Source = #(Get-ChildItem -Recurse -Path \\SERVER\D$\PSTest)
foreach ($file in $Source){
If ($Target = Test-Path #(Get-ChildItem -Recurse -Path \\SERVER\D$\PSTest))
{
$HashResult = (Compare-Object -ReferenceObject $file -DifferenceObject
$Target -Property hash -PassThru).Path
}
else {
$Missing += $file
}
}
Write-Host 'These files have differences.' -ForegroundColor Green
$HashResult
Write-Host 'These files are missing from the target path.' -ForegroundColor
Green
$Missing
When I run that, I don't get any results (other than the text output). Where am I going wrong with this?
Made a few assumptions about the file names and their uniqueness down through the various depths of the source/target folders:
$SourceDir = "C:\\temptest";
$DestDir = "D:\\temptest";
$SourceFiles = #(Get-ChildItem -Recurse -Path $SourceDir);
$DestFiles = #(Get-ChildItem -Recurse -Path $DestDir);
$SourceFileNames = $SourceFiles | % { $_.Name };
$DestFileNames = $DestFiles | % { $_.Name };
$MissingFromDestination = #();
$MissingFromSource = #();
$DifferentFiles = #();
foreach($f in $SourceFiles) {
if (!$DestFileNames.Contains($f.Name)) {
$MissingFromDestination += $f;
} else {
$t = $DestFiles | Where { $_.Name -eq $f.Name };
if ((Get-FileHash $f.FullName).hash -ne (Get-FileHash $t.FullName).hash) {
$DifferentFiles += $f;
}
}
}
foreach($f in $DestFiles) {
if (!$SourceFileNames.Contains($f.Name)) {
$MissingFromSource += $f;
}
}
"
Missing from Destination: "
$MissingFromDestination | % { $_.FullName };
"
Missing from Source: "
$MissingFromSource | % { $_.FullName };
"
Source is Different: "
$DifferentFiles | % { $_.FullName };
This is a bit naive in its approach insofar as it is really only checking file names and ignoring subfolder tree structures. But, hopefully, it will give you enough of a leaping off point.
I am trying to get all files w/in a directory that have the extension ".rtf". I have a working script, but it takes a while, as there is a foreach loop w/in a foreach loop. Is there a faster way to handle this? The goal of the script is to get all files w/in a directory ending in .rtf and use MSWord to Open the file and save it as a ".DOC". The conversion functionality works fine. The issue is with the length of time to search through all of the folders.
Function Convert-Dir($path)
{
$subFolders = get-childitem $path -Recurse | Where-Object {$_.PSIsContainer -eq $True}
if($subFolders)
{
foreach($folder in $subFolders)
{
if($folder.PSisContainer)
{
$Files=Get-ChildItem $folder.fullname -Filter "*.rtf"
$Word=New-Object -ComObject WORD.APPLICATION
if($Files)
{
foreach ($File in $Files)
{
$Doc=$Word.Documents.Open($File.fullname)
$Name=($Doc.name).replace("rtf","doc")
if (Test-Path $Name)
{
} else
{
# Use WORD
$fullName = ($Doc.path + "\" + "Converted_" + $Name)
$Doc.saveas([ref] $fullName, [ref] 0)
$Doc.close()
$fileToRemove = $File.fullName
Remove-Item $fileToRemove
$Word.Quit()
}
}
}
}
}
}
}
I guess the performance is lost by creating a lot of word-instances by calling a word-process in each subfolder. You should should use only one instance of word all the time. Just move the line $Word=New-Object -ComObject WORD.APPLICATION to the top of your function and the line $word.quit() to the very end.
I'm trying to optimize my Powershell Script a little.
I have a lot of log (text) files, that i need to search through the content of, for a specific text entry.
If the entry is found, I need the script to trigger with an inset to an sql databse.
This is what I have for now:
$tidnu = (Get-Date -f dd.MM.yyyy)
$Text = "ERROR MESSAGE STACK"
$PathArray = #()
$NodeName = "SomeName"
$Logfil = "SomeLogFile"
Get-ChildItem $Path -Filter "*ORA11*.log" |
Where-Object { $_.Attributes -ne "Directory"} |
ForEach-Object {
If (Get-Content $_.FullName | Select-String -Pattern $Text)
{
$PathArray += $_.FullName
$cmd.commandtext = "INSERT INTO ErrorTabel (Datotid, Nodename, Logfil, ErrorFound) VALUES('{0}','{1}','{2}','{3}')" -f $tidnu, $NodeName, $Logfil, "Yes"
$cmd.ExecuteNonQuery()
}
else
{
$cmd.commandtext = "INSERT INTO ErrorTabel (Datotid, Nodename, ErrorFound) VALUES('{0}','{1}','{2}')" -f $tidnu, $NodeName, "No"
$cmd.ExecuteNonQuery()
}
}
This is working okay, but when i need to move to another log file name, i have simply made the same code again with different inputs.
What i would like to do, is to use an Array, and a foreach loop, so i could specify all the log files in one array, like:
$LogArray = #(Log1.log, log2.log, log3.log)
And specify all the Nodenames like:
$NodeArray = #(Node1, Node2, Node3)
And then make a foreach loop that will go through the logfiles one by one and insert into the databse, with it's matching nodename every time the loop runs through.
Can someone help me to make this happen? I have the idea on how it should be done, but I can't figure out how to write the code. All help would be much appreciated.
EDIT:
Ok, this is what i have now then, but i'm not sure that it's correct put together. Its giving me some strange results.
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Data Source=PCDK03918;Initial Catalog=Rman;Integrated Security=SSPI;"
$conn.open()
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.connection = $conn
$tidnu = (Get-Date -f dd.MM.yyyy)
$Path = "C:\RMAN"
$Text = "ERROR MESSAGE STACK"
$nodes = #{
'NodeName1' = 'Node1log1.log', 'Node1log2.log', 'Node1log3.log'
'NodeName2' = 'Node2log1.log', 'Node2log2.log'
}
foreach ($NodeName in $nodes.Keys) {
foreach ($Logfil in $nodes[$NodeName]) {
Get-ChildItem $Path -Filter "*.log" |
ForEach-Object {
If (Get-Content $_.FullName | Select-String -Pattern $Text)
{
$cmd.commandtext = "INSERT INTO Error (Datotid, Nodename, Logfil, Error) VALUES('{0}','{1}','{2}','{3}')" -f $tidnu, $NodeName, $Logfil, "Yes"
$cmd.ExecuteNonQuery()
}
else
{
$cmd.commandtext = "INSERT INTO Error (Datotid, Nodename, Logfil, Error) VALUES('{0}','{1}','{2}','{3}')" -f $tidnu, $NodeName, $Logfil, "No"
$cmd.ExecuteNonQuery()
}
}
}
}
$conn.close()
I have created the log files mentioned in $nodes, in the folder, and put the "ERROR MESSAGE STACK" into Node1log1.log and Node1log2.log The rest of the log files are with no "ERROR MESSAGE STACK" inside.
But the result in the database is strange. It says Error = Yes to log files with no "ERROR MESSAGE STACK" inside, and it says Error = No to the same log files some rows down. Plus its inserting double rows and all in all its not doing as intended.
could it be because my
Get-ChildItem $Path -Filter "*.log" |
is wrong by using *.log ?
Or am I simply going completely wrong about this?
EDIT Once more:
Not sure what I was thinking yesterday, but I believe i have solved it now.
Get-ChildItem $Path -Filter "*.log" |
Will of course not work.
Get-ChildItem $Path -Filter $logfil |
Gives much more sense, and now my databse output is looking much more correct.
#Ansgar Wiechers - Thank you for pointing me in the right direction. I learned alot from this.
Consider using a hashtable for this:
$logs = #{
'Log1.log' = 'Node1'
'Log2.log' = 'Node2'
'Log3.log' = 'Node3'
}
That way you can iterate over the logs like this:
foreach ($Logfil in $logs.Keys) {
$NodeName = $logs[$Logfil]
...
}
If you have more than one log file per node name, it would be more efficient to reverse the mapping and store the log file names in an array:
$nodes = #{
'Node1' = 'Log1.log', 'Log2.log', 'Log3.log'
'Node2' = 'Log4.log', 'Log5.log'
}
Then you can process the logfiles with a nested loop like this:
foreach ($NodeName in $nodes.Keys) {
foreach ($Logfil in $nodes[$NodeName]) {
...
}
}
You should be able to fit your pipeline into either loop without further modifications.
Edit: As an optimization you could do something like this to avoid needlessly fetchin logs with each iteration of the outer loop:
$logs = Get-ChildItem $Path -Filter '*.log'
foreach ($NodeName in $nodes.Keys) {
$logs | ? { $nodes[$NodeName] -contains $_.Name } | % {
...
}
}
I'm working on a PowerShell script that finds all the files with PATTERN within a given DIRECTORY, prints out the relevant lines of the document with the PATTERN highlighted, and then replaces the PATTERN with a provided REPLACE word, then saves the file back. So it actually edits the file.
Except I can't get it to alter the file, because Windows complains about the file already being open. I tried several methods to solve this, but keep running into the issue. Perhaps someone can help:
param(
[string] $pattern = ""
,[string] $replace = ""
,[string] $directory ="."
,[switch] $recurse = $false
,[switch] $caseSensitive = $false)
if($pattern -eq $null -or $pattern -eq "")
{
Write-Error "Please provide a search pattern." ; return
}
if($directory -eq $null -or $directory -eq "")
{
Write-Error "Please provide a directory." ; return
}
if($replace -eq $null -or $replace -eq "")
{
Write-Error "Please provide a string to replace." ; return
}
$regexPattern = $pattern
if($caseSensitive -eq $false) { $regexPattern = "(?i)$regexPattern" }
$regex = New-Object System.Text.RegularExpressions.Regex $regexPattern
function Write-HostAndHighlightPattern([string] $inputText)
{
$index = 0
$length = $inputText.Length
while($index -lt $length)
{
$match = $regex.Match($inputText, $index)
if($match.Success -and $match.Length -gt 0)
{
Write-Host $inputText.SubString($index, $match.Index) -nonewline
Write-Host $match.Value.ToString() -ForegroundColor Red -nonewline
$index = $match.Index + $match.Length
}
else
{
Write-Host $inputText.SubString($index) -nonewline
$index = $inputText.Length
}
}
}
Get-ChildItem $directory -recurse:$recurse |
Select-String -caseSensitive:$caseSensitive -pattern:$pattern |
foreach {
$file = ($directory + $_.FileName)
Write-Host "$($_.FileName)($($_.LineNumber)): " -nonewline
Write-HostAndHighlightPattern $_.Line
%{ Set-Content $file ((Get-Content $file) -replace ([Regex]::Escape("[$pattern]")),"[$replace]")}
Write-Host "`n"
Write-Host "Processed: $($file)"
}
The issue is located within the final block of code, right at the Get-ChildItem call. Of course, some of the code in that block is now a bit mangled due to me trying to fix the problem then stopping, but keep in mind the intent of that part of the script. I want to get the content, replace the words, then save the altered text back to the file I got it from.
Any help at all would be greatly appreciated.
Removed my previous answer, replacing it with this:
Get-ChildItem $directory -recurse:$recurse
foreach {
$file = ($directory + $_.FileName)
(Get-Content $file) | Foreach-object {
$_ -replace ([Regex]::Escape("[$pattern]")),"[$replace]")
} | Set-Content $file
}
Note:
The parentheses around Get-Content to ensure the file is slurped in one go (and therefore closed).
The piping to subsequent commands rather than inlining.
Some of your commands have been removed to ensure it's a simple test.
Just a suggestion but you might try looking at the documentation for the parameters code block. There is a more efficient way to ensure that a parameter is entered if you require it and to throw an error message if the user doesn't.
About_throw: http://technet.microsoft.com/en-us/library/dd819510.aspx
About_functions_advanced_parameters: http://technet.microsoft.com/en-us/library/dd347600.aspx
And then about using Write-Host all the time: http://powershell.com/cs/blogs/donjones/archive/2012/04/06/2012-scripting-games-commentary-stop-using-write-host.aspx
Alright, I finally sat down and just typed everything sequentially in PowerShell, then used that to make my script.
It was actually really simple;
$items = Get-ChildItem $directory -recurse:$recurse
$items |
foreach {
$file = $_.FullName
$content = get-content $file
$newContent = $content -replace $pattern, $replace
Set-Content $file $newcontent
}
Thanks for all your help guys.