The PowerShell script below queries the Security event log on one or more servers for events with id 4663. When trying to retrieve all audit events for event id 4663 with the following code the computer throws the following exception: how can we to optimize this PowerShell?
So I just want to fetch security event log based on specific AD Users instead of all of users. Otherwise I want to retrieve what I need.
$server = "HOSTNAME"
$out = New-Object System.Text.StringBuilder
$out.AppendLine("ServerName,EventID,TimeCreated,UserName,File_or_Folder,AccessMask")
$ns = #{e = "http://schemas.microsoft.com/win/2004/08/events/event"}
foreach ($svr in $server)
{ $evts = Get-WinEvent -computer $svr -FilterHashtable #{logname="security";id="4663"} -oldest
foreach($evt in $evts)
{
$xml = [xml]$evt.ToXml()
$SubjectUserName = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[#Name='SubjectUserName']/text()" | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value
$ObjectName = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[#Name='ObjectName']/text()" | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value
$AccessMask = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[#Name='AccessMask']/text()" | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value
$out.AppendLine("$($svr),$($evt.id),$($evt.TimeCreated),$SubjectUserName,$ObjectName,$AccessMask")
Write-Host $svr
Write-Host $evt.id,$evt.TimeCreated,$SubjectUserName,$ObjectName,$AccessMask
}
}
$out.ToString() | out-file -filepath C:\TEMP\4663Events.csv
Refactor the code so that the stringbuilder contents are flushed every now and then. This keeps its size more manageable. Like so,
$out = New-Object System.Text.StringBuilder
$out.AppendLine("ServerName,EventID,TimeCreated,UserName,File_or_Folder,AccessMask")
foreach($evt in $evts) {
$xml = [xml]$evt.ToXml()
...
$out.AppendLine("$($svr),$($evt.id),$($evt.TimeCreated),$SubjectUserName,$ObjectName,$AccessMask")
# If the stringbuffer is large enough, flush its contents to disk
# and start filling empty buffer again
if($out.length -ge 100MB) {
$out.ToString() | out-file -filepath C:\TEMP\4663Events.csv -append
$out.Clear()
}
}
# Remember to flush the buffer so that stuff that wasn't flushed in the
# foreach loop is saved as well
$out.ToString() | out-file -filepath C:\TEMP\4663Events.csv -append
Edit:
As the error is coming from Out-LineOutput, which is an internal cmdlet, it might be about the shell memory settings. You could try increasing the maximum memory per shell to, say 2 GB. Like so,
Set-Item .\MaxMemoryPerShellMB 2048
The MS Scripting Guys blog has a detailed article about configuring the limits
Related
I've been having some issues trying to pass along some variables with the needed output I need.
Summary, I need to grab the CPU utilization of multiple PC, memory of those PC, and HDD usage, I would like the information to come out with what the PC name is followed by each other category of information required listed above.
Below is what I have so far, however, I cant figure out why I cant get the PCName to output for the CPU utilization. The hdd command is working well but I'm stuck trying to string everything together through objects. Thanks for any help.
CPU Script
$computers = Get-Content -Path ()
foreach ($computer in $computers)
$CPUAvg = (get-counter -Counter "\Processor(_Total)\%
Processor Time" -SampleInterval 1 -MaxSamples 5 | select -ExpandProperty countersamples |
select -ExpandProperty cookedvalue | Measure-Object -Average)
Write-Output $CPUAvg
Cant Figure out how to output CPUName for each computer in $computers
HDD Script works fine
$computers = Get-Content -Path ()
Get−CimInstance Win32_LogicalDisk | where{$_.DriveType −eq '3'} `
| Select DeviceID, DriveType,VolumeName,
#{N='TotalSize(GB)';E={[Math]::Ceiling($_.Size/1GB)}}, #{N='FreeSize(GB)';E=
{[Math]::Ceiling($_.FreeSpace/1GB)}} |ft −AutoSize
currently the code always queries the local data and not that one from a remote computer. None of the samples gives the information from the variable $computers back.
In your first sample the foreach statement is missing its expression ({}). But you do not need to loop, as get-counter supports arrays for the parameter "-computername":
get-counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 5 -computername $computers
you get objects back which contain an attribute called CounterSamples, the value contains the computername.
in case of your 2nd sample, you have to specify the parameter "-computername" to specify a list of computers to connect to. also you can do the filtering one step earlier - e.g.
$computers = Get-Content -Path [path]
$result = Get-CimInstance -query "Select DeviceId,DriveType,VolumeName,SystemName from Win32_LogicalDisk where DriveType=3" -ComputerName $computers | Select-Object SystemName,DeviceID,DriveType,VolumeName,#{N='TotalSize(GB)';E={[Math]::Ceiling($_.Size/1GB)}}, #{N='FreeSize(GB)';E={[Math]::Ceiling($_.FreeSpace/1GB)}}
The variable $result contains the information. you can now output the information, like in your sample, only on the screen (ft -Autosize). or you can export it into a csv/json:
#as csv
$result | export-csv C:\my.csv -Delimiter ";" -NoTypeInformation
#as json
$result | convertto-json | set-content C:\my.json -Encoding:utf8
You could also use the invoke-command cmdlet to connect to the remote computers ,query the information and build and return a object containing the necessary information:
$code = {
#Query cpu Info
$counter = get-counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 5
$processorTimeAvg = ($counter.countersamples.cookedvalue | Measure-Object -Average).Average
#Query Disk Info
$diskInfo = Get-CimInstance -query "Select DeviceId,DriveType,VolumeName from Win32_LogicalDisk where DriveType=3" | Select-Object DeviceID,DriveType,VolumeName,#{N='TotalSize(GB)';E={[Math]::Ceiling($_.Size/1GB)}}, #{N='FreeSize(GB)';E={[Math]::Ceiling($_.FreeSpace/1GB)}}
#Build Object to return
$attrsht = #{
computerName=$env:Computername
processorTimeAvg=$processorTimeAvg
deviceId=$diskInfo.DeviceId
driveType=$diskInfo.driveType
volumeName=$diskInfo.volumeName
'totalSize(GB)'=$diskInfo.'totalSize(GB)'
'freeSize(GB)'=$diskInfo.'freeSize(GB)'
}
return New-Object -TypeName psobject -Property $attrsht
}
#Get list of computers
$computers = Get-Content [path]
#Query Computers
$result = Invoke-Command -ComputerName $computers -ScriptBlock $code
#Export result to json
$result | ConvertTo-Json | set-content C:\my.json -Encoding:utf8
Everything works except when a server has two HDD, the code will not output two separate lines of info.
$code = {
#Query cpu Info
$counter = get-counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 5
$processorTimeAvg = ($counter.countersamples.cookedvalue | Measure-Object -Average).Average
#Query Disk Info
$diskInfo = Get-CimInstance Win32_LogicalDisk | where{$_.DriveType -eq '3'} | Select-Object -Property DeviceID, #{'Name' = 'Size (GB)'; Expression={[Math]::Round($_.Size / 1GB,2)}},#{'Name' = 'FreeSpace (GB)'; Expression={[Math]::Round($_.FreeSpace / 1GB,2)}}
#Build Object to return
$attrsht = #{
computerName=$env:Computername
processorTimeAvg=$processorTimeAvg
diskinfo=$diskInfo
}
return New-Object -TypeName psobject -Property $attrsht
}
#Get list of computers
$computers = Get-Content -Path
#Query Computers
$result = Invoke-Command -ComputerName $computers -ScriptBlock $code
#Export result to SAD Folder
$result | Export-Csv -Path
I try to filter Windows events (id=4633) from eventlogs out of a given filestructure recursivly with Get-ChildItem.
The filestructure looks like this:
C:\Temp\raw_data\2018-09\Securitylog\Securitylog_2018-09-14_13-30.evtx
The problem is, that the Get-ChildItem only handles the first level. Every level below the first one seems to be ignored.
I tried using the -Recurse parameter without success. I don't receive any errors, so I believe the syntax is correct. The codesample is given below.
$out = New-Object System.Text.StringBuilder
$out.AppendLine("ServerName,EventID,TimeCreated,UserName,File_or_Folder,AccessMask")
$ns = #{e = "http://schemas.microsoft.com/win/2004/08/events/event"}
Get-ChildItem "C:\temp\raw_data" -Recurse | ForEach-Object {
{
$evts = Get-WinEvent -FilterHashtable #{Path=$_;id="4663"} -Oldest
foreach ($evt in $evts) {
$xml = $evt.ToXml()
$SubjectUserName = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[#Name='SubjectUserName']/text()" |
Select-Object -ExpandProperty Node |
Select-Object -ExpandProperty Value
$ObjectName = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[#Name='ObjectName']/text()" |
Select-Object -ExpandProperty Node |
Select-Object -ExpandProperty Value
$AccessMask = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[#Name='AccessMask']/text()" |
Select-Object -ExpandProperty Node |
Select-Object -ExpandProperty Value
$out.AppendLine("$($svr),$($evt.id),$($evt.TimeCreated),$SubjectUserName,$ObjectName,$AccessMask")
Write-Host $svr
Write-Host $evt.id, $evt.TimeCreated, $SubjectUserName, $ObjectName,$AccessMask
}
}
}
$out.ToString() | Out-File -FilePath "C:\Temp\X4663Events.csv"
I have no idea, why no file is processed. The file will be created, but it is empty execpt for the headline.
It doesn't have to be that hard. Typical powershell looks more like this. Build an object, then export to csv. Know what you're doing, and verify each piece.
$evts = get-winevent security -MaxEvents 3
$objs = foreach ($evt in $evts) {
[xml]$xml = $evt.toxml()
$subjectusername = $xml.event.EventData.data |
where name -eq subjectusername |
select -expand '#text'
$subjectdomainname = $xml.event.EventData.data |
where name -eq subjectdomainname |
select -expand '#text'
[pscustomobject]#{
SubjectUserName = $subjectusername
SubjectDomainName = $subjectdomainname
}
}
$objs
$objs | export-csv mylog.csv
get-content mylog.csv
SubjectUserName SubjectDomainName
--------------- -----------------
SYSTEM NT AUTHORITY
MYCOMP$ AD
MYCOMP$ AD
#TYPE System.Management.Automation.PSCustomObject
"SubjectUserName","SubjectDomainName"
"SYSTEM","NT AUTHORITY"
"MYCOMP$","AD"
"MYCOMP$","AD"
I have around 200 servers and I need to get the disk space & logical drive space details (free space, used space & total space).
Here is my PowerShell query.
$infoObjects = New-Object PSObject
foreach ($machine in $servers) {
$counts = Get-WmiObject -Query "SELECT * FROM Win32_DiskDrive" -ComputerName $machine
$total_disk = #($counts).Count
$i = 0
$total_disk = $total_disk -1
for (; $i -le $total_disk; $i++) {
$a = $i
$a = Get-WmiObject -Query "SELECT * FROM Win32_DiskDrive WHERE DeviceID='\\\\.\\PHYSICALDRIVE$i'" -ComputerName $machine
$b = $i
$b = [math]::round($a.size /1GB)
Add-Member -InputObject $infoObject -MemberType NoteProperty -Name "Physical_Disk $i" -Value $b
}
$infoObject | Export-Csv -Path Inventory.csv -Append -Force -NoTypeInformation
}
It is giving me desired output but if some of serverd have more than one disk or have more logical drive than the output is stuck with that number of drives of first server. It is not giving me output in the CSV file of rest of the drives of other servers.
Here is the example about what I am saying.
ServerName Physical_Disk 0 Physical_Disk 1 Physical_Disk 2 Physical_Disk 3
Server1 100 20 40
Server2 85
Server3 60 450 200 420
Server4 60
Server5 60
Server10 55 20 40
If it seems like I am not able to explain the problem. Let me try again.
First server has 2 physical drives that are coming in my output file (CSV).
Second server also has 2 physical drives that are also in CSV file.
But third server has more than 2 drives and only 2 drives are showing in output.
Export-Csv assumes that all objects in your list are uniform, i.e. that they all have the same properties, just with different values. It takes the properties of the first element to determine what to export. Either make sure that all objects have the same properties, or use a different output method, for instance putting all disk information in an array per host and write that to the output file, e.g. like this:
foreach ($machine in $servers) {
$disks = #($machine)
$disks += Get-WmiObject -Computer $machine -Class Win32_DiskDrive |
ForEach-Object { [Math]::Round($_.Size/1GB) }
$disks -join ',' | Add-Content 'C:\path\to\output.csv'
}
BTW, you don't need multiple WMI queries, since the first one already returns all disks including their sizes.
I would like to point that there is several places where error could occur. The purpose of this answer is to address the unknown number of headers. I would recommend that you run this in place to see what it is trying to show you before you attempt to integrate this.
# Gather all wmi drives query at once
$alldisksInfo = Get-WmiObject –query "SELECT * FROM Win32_DiskDrive" -ComputerName $servers -ErrorAction SilentlyContinue | Group-Object __Server
# Figure out the maximum number of disks
$MaximumDrives = $alldisksInfo | Measure-Object -Property Count -Maximum | Select-Object -ExpandProperty Maximum
# Build the objects, making empty properties for the drives that dont exist for each server where need be.
$servers | ForEach-Object{
# Clean the hashtable
$infoObject = [ordered]#{}
# Populate Server
$infoObject.Server = $_
# Add other simple properties here
$infoObject.PhysicalMemory = (Get-WmiObject Win32_PhysicalMemory -ComputerName $infoObject.Server | Measure-Object Capacity -Sum).Sum/1gb
# Add the disks information from the $diskInfo Array
$serverDisksWMI = $alldisksInfo | Where-Object{$_.Name -eq $infoObject.Server} | Select-Object -ExpandProperty Group
for($diskIndex =0; $diskIndex -lt $MaximumDrives;$diskIndex++){
$infoObject."PhysicalDisk$diskIndex" = [Math]::Round(($serverDisksWMI | Where-Object{($_.DeviceID -replace "^\D*") -eq $diskIndex} | Select -Expand Size)/1GB)
}
# Create the custom object now.
New-Object -TypeName psobject -Property $infoObject
} # | Export-Csv ....
Since this uses the pipeline you can easily add export-CSV on the end of this.
I Have below script:
And I am looking for help to convert the output to Excel format
$Pather = Get-Content C:\Servers.txt
foreach($Path in $Pather)
{
(Get-Acl $Path).access | ft $path,IdentityReference,FileSystemRights,AccessControlType,IsInherited -auto
}
How can I make this use a list of servers
Tried doing $ComputerList = gc <list location> but it doesnt seem to be working
correctly with one computer
$Start = (Get-Date).AddMinutes(-120)
$ComputerList = $env:ComputerName
$Events = gc C:\Temp\ErrorCodes.txt
# Getting all event logs
Get-EventLog -AsString -ComputerName $Computername |
ForEach-Object {
# write status info
Write-Progress -Activity "Checking Eventlogs on \\$ComputerName" -Status $_
# get event entries and add the name of the log this came from
Get-EventLog -LogName $_ -EntryType Error, Warning -After $Start -ComputerName $ComputerName -ErrorAction SilentlyContinue |
Add-Member NoteProperty EventLog $_ -PassThru | Where-Object {$Events -contains $_.eventid}
} |
# sort descending
Sort-Object -Property EventLog |
# select the properties for the report
Select-Object EventLog, EventID, TimeGenerated, EntryType, Source, Message
# output into grid view window
Out-GridView -Title "All Errors & Warnings from \\$Computername"
Force it to be an array.. Otherwise it will come in as a string if there is only one item
$ComputerList = #(gc c:\folder\computerlist.txt)
PowerShell: How can I to force to get a result as an Array instead of Object
I have a script that scans all my servers in my domains and outputs to two separate CSV files - one simple and one extensive.
I write one line at the time to my csv.. This results in thousands of file-open and file-close.. I've lurked around and understand that I should first gather all the info and write it all in one sweep at the end of the script. But how do I do this with export-csv (preferably using a function)?
And is there a way I can use the same function for short and long list?
The script performs numerous tasks on each domain/server, but I've trimmed it down to this for your viewing pleasure:
$domains = "no","se","dk"
# Loop through specified forests
foreach ($domain in $domains) {
# List all servers
$servers = Get-QADComputer
# Looping through the servers
foreach ($server in $servers) {
# GENERATE LONGLIST #
# Ping server
if (Test-Connection -ComputerName $server.name -count 1 -Quiet )
{
$Reachable = "Yes"
# Fetch IP address
$ipaddress = [System.Net.Dns]::GetHostAddresses($Server.name)|select-object IPAddressToString -expandproperty IPAddressToString
# Formatting output and export all info to CSV
New-Object -TypeName PSObject -Property #{
SystemName = ($server.name).ToLower()
Reachable = $Reachable
Domain = $server.domain
IPAddress = $IPAddress
} | Select-Object SystemName,Domain,IPAddress| Export-Csv -Path "longexport.csv" -append
}
else # Can't reach server
{
$reachable = "No"
$IPAddress = "Unknown"
# Formatting output and export all info to CSV
New-Object -TypeName PSObject -Property #{
SystemName = ($server.name).ToLower()
Reachable = $Reachable
Domain = $server.domain
} | Select-Object SystemName,Domain,IPAddress| Export-Csv -Path "shortexport.csv" -append
}
}
}
(and let me just say that I know that I cannot do -append with export-csv, but I am using a function that let's me do this..)
You are exporting the same amount of properties to each file so I'm not sure I understand why one of them is considered long and one short. Anyway, I suggest the following, don't assign all computers to a variable, it can take up a lot of RAM, instead use a streaming way (one object at a time) and use foreach-object. Also, since I find no difference in the files I output to the file at the end of each domain operation (one per domain). And with another twick you could write only once to the file.
$domains = "no","se","dk"
foreach ($domain in $domains) {
Get-QADComputer -Service $domain -SizeLimit 0 | Foreach-Object {
$reachable = Test-Connection -ComputerName $_.Name -count 1 -Quiet
if($reachable)
{
$IPAddress = [System.Net.Dns]::GetHostAddresses($_.Name)|select-object IPAddressToString -expandproperty IPAddressToString
}
else
{
$IPAddress = $null
}
New-Object -TypeName PSObject -Property #{
SystemName = $_.Name.ToLower()
Reachable = $reachable
Domain = $_.Domain
IPAddress = $IPAddress
} | Select-Object SystemName,Domain,IPAddress
} | Export-Csv -Path export.csv -Append
}
You'll need to keep data in memory to prevent multiple file open/closes.
You can do this by adding your data to an array like this:
$myData = #()
$myData += New-Object -TypeName PSObject -Property #{
SystemName = ($server.name).ToLower()
Reachable = $Reachable
Domain = $server.domain
} | Select-Object SystemName,Domain,IPAddress
Then at the end of processing convert the array to CSV using $myData | ConvertTo-CSV | Out-File C:\Data.csv or just $myData | Export-Csv C:\Data.csv.