I am trying to remove vulnerable classes from the log4j jar file with powershell.
I am able to remove the file using the script locally on the server, however, I want to remove the class from many paths on many servers and trying to use invoke-command.
The script can open and read the JAR file but doesnt seem to action the delete() method. Is there a way to get powershell to delete the class "remotely"?
Here is my script:
$servers = #(
"server"
)
$class_to_delete = "JMSSink"
$unable_to_connect = #()
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.Filesystem
write-host "`nTesting connection to:" -ForegroundColor Yellow
$servers | ForEach-Object {Write-Host "$_"}
$servers | ForEach-Object {
$server = $_
try {
write-host "`nTesting $($server)"
Invoke-Command -ComputerName $server -ScriptBlock {
Write-Host "Connection successful to $($env:computername)" -ForegroundColor Green
} -ErrorAction Stop
} catch {
write-host "`nConnection failed to $($server)"
$unable_to_connect += $server
}
}
Write-Host "`nStarting script to remove $($class_to_delete) class from log4j" -ForegroundColor Yellow
$objects_skipped = #()
$servers | ForEach-Object {
$server_node = $_
write-host "`nPut in the file paths for $($server_node)" -ForegroundColor Yellow
$file_locations = (#(While($l=(Read-Host).Trim()){$l}) -join("`n"))
$file_locations | Out-File C:\temp\output.txt #Change this path to the temp folder and file on the server you execute from
$file_objects = Get-Content -Path C:\temp\output.txt #Change this path to the temp folder and file on the server you execute from
$stats_x = foreach ($file_object in $file_objects) {
$stats = Invoke-Command -ComputerName $server_node -ScriptBlock {
Write-Host "`nStarting on $($env:COMPUTERNAME)"
$class = $using:class_to_delete
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.Filesystem
$ful_path = $using:file_object
$fn = Resolve-Path $ful_path
try {
$zip = [System.io.Compression.ZipFile]::Open("$fn", "Read")
Write-Host "Backing up $($fn) to $($fn).bak"
$zip.Dispose()
Copy-Item "$fn" "$($fn).bak"
$zip = [System.io.Compression.ZipFile]::Open($fn, "Update")
$files = $zip.Entries | Where-Object { $_.name -eq "$($class).class" }
if (!$files) {
write-host "`nNo $($class) class found on $($env:COMPUTERNAME) for path: $($ful_path)"
$files.dispose()
$not_found = #()
$not_found += New-Object -TypeName PSObject -Property #{
Server = $env:COMPUTERNAME;
Path = $ful_path;
Result = "$($class) class NOT FOUND"
}
Write-Output $not_found
} else {
foreach ($file in $files) {
write-host "`n$($class) class found on $($env:COMPUTERNAME) for path: $($ful_path)"
write-host "`nDeleting file $($file)" -ForegroundColor Green
#delete class
$file.delete()
#check if class was successfully deleted
$confirm_delete = $zip.Entries | Where-Object { $_.name -eq "$($class).class" }
write-host $confirm_delete
if ($confirm_delete.name -match "$class.class") {
$deleted_status = "$($class) !!NOT REMOVED!!"
} else {
$deleted_status = "$($class) REMOVED"
}
$Output = #()
$Output += New-Object -TypeName PSObject -Property #{
Server = $env:COMPUTERNAME;
Path = $ful_path;
Result = $deleted_status
}
Write-Output $Output
}
}
} catch {
Write-Host "Cannot open $($ful_path) as a Zip File. $($Error[0])"
}
}
Write-Output $stats
}
$objects_skipped += $stats_x
}
#result
write-host "`nEnd result"
$objects_skipped | select Server,Result,Path | ft -AutoSize
You need to explicitly call Dispose() on the containing archive to persist the updates to the file on disk:
# this goes immediately after the `catch` block:
finally {
if($zip -is [IDisposable]){ $zip.Dispose() }
}
By placing the call to $zip.Dispose() inside a finally block, we ensure that it's always disposed regardless of whether an exception was thrown in the preceding try block.
Related
Can someone point me in the right direction? Basically, I would like to export the results of my testpath to a csv. Below is what I am working with. I have read a couple Microsoft documents but they only seem to confuse me even more. Any feedback is appreciated.
$ComputerList = (Get-ADComputer -Filter *).name
$ComputerList
write-host "`n"
Foreach ($Computer in $ComputerList)
{
$userfolders = get-childitem "\\$Computer\C$\users\"
foreach ($user in $userfolders) {
$ErrorActionPreference= 'silentlycontinue'
$path = $user.fullname
write-host $path
$t = test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default"
IF ($t -eq 'True') {write-host "Has it" -ForegroundColor yellow} ELSE {write-host "no"}
write-host "`n"
}
$Output =New-Object -TypeName PSObject -Property #{
} | Select-Object
}
$Output | C:\Users\"user"\Chrome.csv
write-output "Script finished. Please check output files"
Assuming you want a record per user per computer, there's two things you want to change structurally:
Create new objects in the inner foreach loop
Assign all the objects created to $Output:
$ComputerList = (Get-ADComputer -Filter *).name
$ComputerList
write-host "`n"
$Output = Foreach ($Computer in $ComputerList) {
$userfolders = get-childitem "\\$Computer\C$\users\"
foreach ($user in $userfolders) {
$ErrorActionPreference = 'silentlycontinue'
$path = $user.fullname
write-host $path
$t = test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default"
IF ($t -eq 'True') {write-host "Has it" -ForegroundColor yellow} ELSE {write-host "no"}
write-host "`n"
New-Object -TypeName PSObject -Property #{
# We still need a bit of magic here
}
}
}
$Output | C:\Users\"user"\Chrome.csv
write-output "Script finished. Please check output files"
Now we just need to decide on what properties to add to our output objects:
New-Object -TypeName PSObject -Property #{
# We definitely want to know which computer and user profile the results are for!
ComputerName = $Computer
ProfileName = $user.Name
# And finally we want the results of `Test-Path`
Result = $t
}
Here's another option. Though nowhere near as elegant as what Matthias gave you. ;-}
It's just a refactor, to narrow down your code and pass everything directly and output by default, without the need for all the, Write-* stuff and the like. PowerShell just grants a number of ways to accomplish a use case.
Clear-Host
$null = New-Item -Path 'C:\Temp\Chrome.csv' -Force
$Status = $null
$env:COMPUTERNAME,'Localhost', '127.0.0.1' |
Foreach {
Get-ChildItem "\\$PSItem\C$\users\" |
foreach {
$ErrorActionPreference = 'silentlycontinue'
# Use variable squeezing to assign and output to the screen
($path = $PSItem.fullname)
If (test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default") {$Status = 'Has it'}
Else {$Status = 'no'}
}
[PSCustomObject] #{
ComputerName = $PSItem
Status = $Status
} | Export-Csv -Path 'C:\Temp\Chrome.csv' -Append
}
'Script finished. Please check output files'
# Results on screen
<#
\\104DB2FE-76B8-4\C$\users\ContainerAdministrator
\\104DB2FE-76B8-4\C$\users\ContainerUser
\\104DB2FE-76B8-4\C$\users\Public
\\104DB2FE-76B8-4\C$\users\WDAGUtilityAccount
\\Localhost\C$\users\ContainerAdministrator
\\Localhost\C$\users\ContainerUser
\\Localhost\C$\users\Public
\\Localhost\C$\users\WDAGUtilityAccount
\\127.0.0.1\C$\users\ContainerAdministrator
\\127.0.0.1\C$\users\ContainerUser
\\127.0.0.1\C$\users\Public
\\127.0.0.1\C$\users\WDAGUtilityAccount
Script finished. Please check output files
#>
Import-Csv -Path 'C:\Temp\Chrome.csv'
# Results
<#
104DB2FE-76B8-4 no
Localhost no
127.0.0.1 no
#>
Clear-Host
$null = New-Item -Path 'C:\Temp\Chrome.csv' -Force
$Status = $null
$env:COMPUTERNAME,'Localhost', '127.0.0.1' |
Foreach {
Get-ChildItem "\\$PSItem\C$\users\" |
foreach {
$ErrorActionPreference = 'silentlycontinue'
# Use variable squeezing to assign and output to the screen
($path = $PSItem.fullname)
If (test-path -Path "$path\AppData\Local\MicrosoftEdge") {$Status = 'Has it'}
Else {$Status = 'no'}
}
[PSCustomObject] #{
ComputerName = $PSItem
Status = $Status
} | Export-Csv -Path 'C:\Temp\Chrome.csv' -Append
}
'Script finished. Please check output files'
# Results
<#
\\104DB2FE-76B8-4\C$\users\ContainerAdministrator
\\104DB2FE-76B8-4\C$\users\ContainerUser
\\104DB2FE-76B8-4\C$\users\Public
\\104DB2FE-76B8-4\C$\users\WDAGUtilityAccount
\\Localhost\C$\users\ContainerAdministrator
\\Localhost\C$\users\ContainerUser
\\Localhost\C$\users\Public
\\Localhost\C$\users\WDAGUtilityAccount
\\127.0.0.1\C$\users\ContainerAdministrator
\\127.0.0.1\C$\users\ContainerUser
\\127.0.0.1\C$\users\Public
\\127.0.0.1\C$\users\WDAGUtilityAccount
Script finished. Please check output files
#>
Import-Csv -Path 'C:\Temp\Chrome.csv'
# Results
<#
ComputerName Status
------------ ------
104DB2FE-76B8-4 Has it
Localhost Has it
127.0.0.1 Has it
#>
I know this question is already answered, but that question didn't answered my question.
The link is Adding PSCustomObject to Array gives Error, but works fine when debugging the code in Visual Studio Code
When I am trying to run the script its executing fine in debug mode but its failing if I am executing directly.
$path = Split-Path $script:MyInvocation.MyCommand.Path
$output_File = $path + "\$outputFile_Name"
$report = #()
function Folder-Access
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string]$prod_path,
[Parameter(Mandatory=$true, Position=1)]
[string]$non_prod_path
)
Write-Host "`$prod_path value: $prod_path"
Write-Host "`$non_prod_path value: $non_prod_path"
try {
$prod_acl = get-acl $prod_path
$nonProd_acl = Get-Acl $non_prod_path
$arr = #()
foreach ($non_prod_usr in ($nonProd_acl.access) ) {
$x = $non_prod_usr.IdentityReference.Value
$arr += $x
}
foreach ($usr in ($prod_acl.Access)){
$dirObj = New-Object psobject
if ($usr.IdentityReference.Value -in $arr) {
echo "Do Nothing."
} else {
if ($usr.IdentityReference.Value -ne 'BUILTIN\Administrators') {
if ($usr.IdentityReference.Value -ne 'S-1-5-21-2554127390-2720852439-1129525235-1959308') {
$properties = [ordered]#{'Foldername'=$non_prod_path; 'AD Group'=$usr.IdentityReference.Value;
'Permissions'=$usr.Filesystemrights}
echo "Do Something."
$usr.IdentityReference.Value
#$dirObj | Add-Member -MemberType NoteProperty -Name "Foldername" -Value $non_prod_path
#$dirObj | Add-Member -MemberType NoteProperty -Name 'AD Group' -Value $usr.IdentityReference.Value
#$dirObj | Add-Member -MemberType NoteProperty -Name 'Permissions' -Value $usr.Filesystemrights
#$report += $dirObj
$report += New-Object -TypeName psobject -Property $properties
}
}
}
#$report += New-Object -TypeName psobject -Property $properties
} $report | Export-Csv -Path 'C:\Powershell\output.csv' -Append -NoTypeInformation -Encoding UTF8
}
catch {
Write-Host "Error occured " + $_ -ForegroundColor Red
}
}
function executionTimeForSourcePath {
try {
$Prod_Folder_Path = Get-ChildItem -Directory -Path $prod_path -Recurse -Force
$Prod_Folder_Path | ForEach-Object {
if (-not ([string]::IsNullOrEmpty($_.FullName))){
$path = $_.FullName
$folderName = $path.Replace($prod_path + '\',"")
if (-Not (Test-Path "$non_prod_path\$folderName")){
try {
New-Item -ItemType Dir -Path "$non_prod_path\$folderName" -ErrorAction Stop
if (Test-Path "$non_prod_path\$folderName"){
Folder-Access -prod_path $path -non_prod_path "$non_prod_path\$folderName"
}
}
catch {
Write-Host $_ -ForegroundColor Red
}
}
ElseIf (Test-Path "$non_prod_path\$folderName") {
Folder-Access -prod_path $path -non_prod_path "$non_prod_path\$folderName"
}
} }
} catch { $_ }
}
echo "Script started.."
executionTimeForSourcePath
Would be great if someone can guide me here please. I tried all the possible ways but none worked for this.
The error is
Method invocation failed because [System.Management.Automation.
PSObject] does not contain a method named 'op_Addition'.
Looking for advice on error handling in Powershell. I think I understand the concept behind using Try/Catch but I'm struggling on where to utilize this in my scripts or how granular I need to be.
For example, should I use the try/catch inside my functions and if so, should I insert the actions of my function inside the try or do I need to break it
down further? OR, should I try to handle the error when I call my function? Doing something like this:
Try{
Get-MyFunction
} catch{ Do Something"
}
Here's an example of a script I wrote which is checking for some indicators of compromise on a device. I have an application that will launch this script and capture the final output. The application requires the final output to be in the following format so any failure should generate this.
[output]
result=<0 or 1>
msg= <string>
Which I'm doing like this:
Write-Host "[output]"
Write-Host "result=0"
Write-Host "msg = $VariableContainingOutput -NoNewline
Two of my functions create custom objects and then combine these for the final output so I'd like to capture any errors in this same format. If one function generates an error, it should record these and continue.
If I just run the code by itself (not using function) this works but with the function my errors are not captured.
This needs to work on PowerShell 2 and up. The Add-RegMember and Get-RegValue functions called by this script are not shown.
function Get-ChangedRunKey {
[CmdletBinding()]
param()
process
{
$days = '-365'
$Run = #()
$AutoRunOutput = #()
$RunKeyValues = #("HKLM:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\Software\Wow6432node\Microsoft\Windows\CurrentVersion\Run",
"HKU:\S-1-5-21-*\Software\Microsoft\Windows\CurrentVersion\Run",
"HKU:\S-1-5-21-*\Software\Wow6432node\Microsoft\Windows\CurrentVersion\Run"
)
Try{
$Run += $RunKeyValues |
ForEach-Object {
Get-Item $_ -ErrorAction SilentlyContinue |
Add-RegKeyMember -ErrorAction SilentlyContinue |
Where-Object {
$_.lastwritetime -gt (Get-Date).AddDays($days)
} |
Select-Object Name,LastWriteTime,property
}
if ($Run -ne $Null)
{
$AutoRunPath = ( $Run |
ForEach-Object {
$_.name
}
) -replace "HKEY_LOCAL_MACHINE", "HKLM:" -replace "HKEY_Users", "HKU:"
$AutoRunValue = $AutoRunPath |
Where-Object {
$_ -and $_.Trim()
} |
ForEach-Object {
Get-RegValue -path $_ -Name '*' -ErrorAction SilentlyContinue
}
}
#Build Custom Object if modified Run keys are found
if($AutorunValue -ne $null)
{
foreach ($Value in $AutoRunValue) {
$AutoRunOutput += New-Object PSObject -Property #{
Description = "Autorun"
path = $Value.path
value = $Value.value
}
}
}
Write-Output $AutoRunOutput
}catch{
$AutoRunOutput += New-Object PSObject -Property #{
Description = "Autorun"
path = "N/A"
value = "Error accessing Autorun data. $($Error[0])"
}
}
}
}
function Get-ShellIOC {
[CmdletBinding()]
param()
process
{
$ShellIOCOutput = #()
$ShellIOCPath = 'HKU:\' + '*' + '_Classes\*\shell\open\command'
Try{
$ShellIOCValue = (Get-Item $ShellIOCPath -ErrorAction SilentlyContinue |
Select-Object name,property |
ForEach-Object {
$_.name
}
) -replace "HKEY_LOCAL_MACHINE", "HKLM:" -replace "HKEY_Users", "HKU:"
$ShellIOCDetected = $ShellIOCValue |
ForEach-Object {
Get-RegValue -path $_ -Name '*' -ErrorAction SilentlyContinue
} |
Where-Object {
$_.value -like "*cmd.exe*" -or
$_.value -like "*mshta.exe*"
}
if($ShellIOCDetected -ne $null)
{
foreach ($ShellIOC in $ShellIOCDetected) {
$ShellIOCOutput += New-Object PSObject -Property #{
Description = "Shell_IOC_Detected"
path = $ShellIOC.path
value = $ShellIOC.value
}
}
}
Write-Output $ShellIOCOutput
}catch{
$ShellIOCOutput += New-Object PSObject -Property #{
Description = "Shell_IOC_Detected"
path = "N/A"
value = "Error accessing ShellIOC data. $($Error[0])"
}
}
}
}
function Set-OutputFormat {
[CmdletBinding()]
param()
process
{
$FormattedOutput = $AutoRunOutput + $ShellIOCOutput |
ForEach-Object {
"Description:" + $_.description + ',' + "Path:" + $_.path + ',' + "Value:" + $_.value + "|"
}
Write-Output $FormattedOutput
}
}
if (!(Test-Path "HKU:\")){
try{
New-PSDrive -PSProvider Registry -Root HKEY_USERS -Name HKU -ErrorAction Stop | Out-Null
}catch{
Write-Output "[output]"
Write-Output "result=0"
Write-Host "msg = Unable to Connect HKU drive" -NoNewline
}
}
$AutoRunOutput = Get-ChangedRunKey
$ShellIOCOutput = Get-ShellIOC
$FormattedOutput = Set-OutputFormat
Write-Output "[output]"
if ($FormattedOutput -eq $Null)
{
Write-Output "result=0"
Write-Host "msg= No Items Detected" -NoNewline
}
else
{
Write-Output "result=1"
Write-Host "msg=Items Detected: $($FormattedOutput)" -NoNewline
}
You have to know that there are 2 error types in PowerShell:
Terminating Errors: Those get caught automatically in the catch block
Non-Terminating Error: If you want to catch them then the command in question needs to be execution using -ErrorAction Stop. If it is not a PowerShell command but an executable, then you need to check stuff like the exit code or $?. Therefore I suggest wrapping your entire action in an advanced function on which you then call using -ErrorAction Stop.
Apart from that I would like to remark that PowerShell version 2 has already been deprecated. The reason for why non-terminating errors exists is because there are cases like for example processing multiple objects from the pipeline where you might not want it to stop just because it did not work for one object. And please do not use Write-Host, use Write-Verbose or Write-Output depending on the use case.
I'm trying (and failing) to:
Connect to the server by iterating through a list.
Confirm location where file exists (1 of 3 locations).
Replace a string in that file.
I've tried to do this multiple ways. There are two that I have which do part of what I want.
Can someone please help me understand if there's something I'm doing inefficiently or how to put all this together?
This one can loop through the servers and find the file
$ErrorActionPreference = 'SilentlyContinue'
$nope=$null
$servers= Get-Content C:\Servers.txt
foreach ($server in $servers)
{
If (Test-Connection -ComputerName $server -Quiet)
{Invoke-Command -ComputerName $server -ScriptBlock {$file=(Get-Childitem -Path C:\DiskSpace.ps1, D:\DiskSpace.ps1, Y:\DiskSpace.ps1); Write-Host "Found $file on $env:computername."}}
Else {
Write-Host ">> Could not connect to $server."; $nope += $server}
}
Write-Host $nope
...and this one can at least find a local file
$valid=#('')
$paths = #("C:\Users\user_name\Desktop\DiskSpace.ps1","C:\DiskSpace.ps1","D:\DiskSpace.ps1","Y:\DiskSpace.ps1")
Foreach ($path in $paths)
{
if (Test-Path $path)
{$valid += $path}
}
write-host $valid
Here's how I intend to to replace the string:
$ErrorActionPreference = 'SilentlyContinue'
$find=(Get-Childitem -Path C:\, D:\, Y:\ -include DiskSpace.ps1 -Recurse)
Write-Host $find
$ErrorActionPreference = 'Stop'
try {
(Get-Content $find).replace('bad_email#domain.com', 'good_email#domain.com') | Set-Content $find
}
catch {
}
Get-Content $find
You had all the pieces already. Simply loop over your Get-Content command for each file in the Invoke-Command.
$ErrorActionPreference = 'SilentlyContinue'
$servers = Get-Content C:\Servers.txt
$files = #('C:\DiskSpace.ps1', 'D:\DiskSpace.ps1', 'Y:\DiskSpace.ps1')
$report = foreach ($server in $servers) {
if (Test-Connection -ComputerName $server -Quiet) {
$response = Invoke-Command -ComputerName $server -ScriptBlock {
Get-Childitem -Path $using:files | ForEach-Object {
(Get-Content $_).replace('bad_email#domain.com', 'good_email#domain.com') | Set-Content $_
[PSCustomObject]#{
Name = $env:COMPUTERNAME
Message = "$($_.fullname) updated."
}
}
}
if ($response -eq $null) {
[PSCustomObject]#{
Name = $env:COMPUTERNAME
Message = "No files found"
}
} else {
$response
}
} else {
[PSCustomObject]#{
Name = $env:COMPUTERNAME
Message = "Unreachable"
}
}
}
$report
I have the below PowerShell script which I want to run from command prompt but it gives me Cannot find the path error for files ServerList.txt and Urls.txt. Script works when I change directory to the folder where the script and files exists.
write-host "********* Changing IE Settings********************"
$servers = Get-Content .\ServerList.txt
$Urls = Get-Content .\Urls.txt
$command ={
$registryPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\InternetSettings\ZoneMap\Domains"
Foreach ($url in $Urls)
{
$checkRegistryPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\" + $url
if(!(Test-Path $checkRegistryPath))
{
write-host "Adding url to local intranet"
if($url -eq "localhost")
{
$key = (get-item HKCU:\).OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains", $true)
$subkey=$key.CreateSubKey('localhost')
$subkey.SetValue("http","1","DWORD")
$subkey.SetValue("https","1","DWORD")
$key.Close()
$subkey.Close()
}
elseif($url -like '*system*')
{
$key = (get-item HKCU:\).OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains", $true)
$subkey = $key.CreateSubKey('//system')
$subkey.SetValue("hcp","1","DWORD")
$key.Close()
$subkey.Close()
}
elseif($url -like '*next.loc*')
{
$key = (get-item HKCU:\).OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains", $true)
$key.CreateSubKey("next.loc")
$serverkey =(get-item HKCU:\).OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\next.loc", $true)
$servername= (([System.Uri]$url).Host).split('.')
$subkey=$serverkey.CreateSubKey($servername[0])
$subkey.SetValue("http","1","DWORD")
$key.Close()
$serverkey.Close()
$subkey.close()
}
}
else
{
write-host $url "url already added to local intranet"
}
}
}
Foreach ($server in $servers)
{
if([string]::IsNullOrEmpty($server))
{
Invoke-Command -ScriptBlock $command
}
else
{
Invoke-Command -Computer $server -ScriptBlock $command
}
}
write-host "****** IE Settings Changed Sucessfully************"
You can determine the path of your script using:
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
Now you can use the $scriptPath to combine your path using the Join-Path cmdlet:
$servers = Get-Content (Join-Path $scriptPath 'ServerList.txt')
$Urls = Get-Content (Join-Path $scriptPath 'Urls.txt')