I have been tasked to export all file sizes individually in all the shared folder exists on a computer except the system shares with ACL and Shared permissions.
Something like Treesize output with the Shared and ACL permissions.
I have tried the below code but it is not showing what I need in output.
Any help will be greatly appreciated.
function Get-ShareSize {
Param(
[String[]]$ComputerName = $env:computername
)
Begin{$objFldr = New-Object -com Scripting.FileSystemObject}
Process{
foreach($Computer in $ComputerName){
Get-WmiObject Win32_Share -ComputerName $Computer -Filter "not name like '%$'" | %{
$Path = $_.Path -replace 'C:',"\\$Computer\c$"
$Size = ($objFldr.GetFolder($Path).Size) / 1GB
New-Object PSObject -Property #{
Name = $_.Name
Path = $Path
Description = $_.Description
Size = $Size
}
}
}
}
}
Get-ShareSize -ComputerName localhost
Your code already looks quite good, but..
The way you use -Filter is wrong and also the part where you convert the $_.Path into a UNC path is not correct.
Apart from that, there is no need to us a Com object (Scripting.FileSystemObject) to get the actual size of the share.
Try this
function Get-ShareSize {
Param(
[String[]]$ComputerName = $env:computername
)
foreach($Computer in $ComputerName){
Get-WmiObject Win32_Share -ComputerName $Computer | Where-Object { $_.Name -notlike '*$' } | ForEach-Object {
# convert the Path into a UNC pathname
$UncPath = '\\{0}\{1}' -f $Computer, ($_.Path -replace '^([A-Z]):', '$1$')
# get the folder size
try {
$Size = (Get-ChildItem $UncPath -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1GB
}
catch {
Write-Warning "Could not get the file size for '$uncPath'"
$Size = 0
}
# output the details
[PSCustomObject]#{
'Name' = $_.Name
'LocalPath' = $_.Path
'UNCPath' = $UncPath
'Description' = $_.Description
'Size' = '{0:N2} GB' -f $Size # format the size to two decimals
}
}
}
}
Get-ShareSize -ComputerName localhost
Hope that helps
Related
I had this script, but the format was in HTML and I cleaned up all the code and changed the commands that were "gwmi" to "Get-CimInstance" to have good practices. My goal is to transform this script that handles a hostname to a list of hostnames.
Can someone help me?
The idea I had would be to have a window that has a "browse" button to import the list, be it in txt or csv and for each hostname in that list it would do these commands and in the end it would export to an xlsx file (I tried with Export-Excel, but you need to download the module separately, and you need to trust the repository, authorize the import of the module for later use, so I would have to make this standalone, without any request, because I would convert this ps1 into an exe file) and the data, would need to be side by side with the headers, e.g. Hostname, Last User Logged, Type Of Chassis etc.
I would be very grateful if someone can help me, I've been building part 1 of this script for a few hours now, and now I need to go to part 2 (that is this process to create and export csv results) which is the part where I feel stuck.
Add-Type -AssemblyName Microsoft.VisualBasic
$ComputerName = [Microsoft.VisualBasic.Interaction]::InputBox("Insert the hostname Name", "Hardware Report")
If ($ComputerName -eq "")
{
break
}
#Check computer online in network, if is not online, the hostname will be skipped but necessary add in log entry which computers is offline
$ProgressPreference = 'SilentlyContinue'
try {
$ErrorActionPreference = "Stop";
$TestComputerHost = Test-Connection $ComputerName -Count 1 -InformationAction Continue -WarningAction SilentlyContinue;
} catch {
#Hostname will be skipped
} finally {
$ProgressPreference = 'Continue'
$ErrorActionPreference = "Continue"
}
#Validade crucial service that is crucial for get remote data, and if is not possible to get this information, the hostname will be skipped
try {
$ErrorActionPreference = "Stop"
Get-CimInstance win32_operatingsystem -ComputerName $ComputerName | Out-Null }
catch [System.Runtime.InteropServices.COMException]
{
#Hostname will be skipped
}
#Validate if the WS Management service is enabled on the remote device
$ProgressPreference = 'SilentlyContinue'
$TestComputerHost = Test-NetConnection $ComputerName -Port 5985 -InformationLevel Quiet -WarningAction SilentlyContinue
If ($TestComputerHost -ne "False"){
}
$ProgressPreference = 'Continue'
#Function to create the Get-WUChassisType that is performed to find out if the Chassis of the equipment is Notebook or Desktop, and it is not configured to detect virtual machine
Function Get-WUChassisType {
[CmdletBinding()]
param (
)
Set-StrictMode -Version 'Latest'
[int[]]$chassisType = try {
$ErrorActionPreference = "Stop";
Get-CimInstance Win32_SystemEnclosure -ComputerName $ComputerName | Select-Object -ExpandProperty ChassisTypes;
} catch {
#Here need to be blank result or skip this result but keep the rest results
} finally {
$ErrorActionPreference = "Continue";
}
switch ($chassisType) {
{ $_ -in 3, 4, 5, 6, 7, 15, 16 } {
return 'Desktop'
}
{ $_ -in 8, 9, 10, 11, 12, 14, 18, 21, 31, 32 } {
return 'Notebook'
}
{ $_ -in 30 } {
return 'Tablet'
}
{ $_ -in 17, 23 } {
return 'Servidor'
}
Default {
}
}
}
#Function to get last logged user on remote computer
Function Get-LastUser {
try {
$ErrorActionPreference = "Stop"
Get-WmiObject Win32_LoggedOnUser -ComputerName $ComputerName |
Select Antecedent -Unique |
% {
$domain = $_.Antecedent.Split('"')[1]
if($domain -eq "DOMAIN") {
"{0}\{1}" -f $domain, $_.Antecedent.Split('"')[3]
}
} | Select-Object -First 1
} catch [System.Runtime.InteropServices.COMException]
{
}
}
#Name of remote computer
$Name = 'Hostname' + $ComputerName
#Get last logged user (by function)
$LastLoggedUser = ((Get-LastUser).Split('\')[1])
#Last Boot Time
$LastBoot = (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName |Select-Object CSName, LastBootUpTime | Select -ExpandProperty LastBootUpTime).tostring("dd/MM/yyyy hh:mm:ss")
#Chassis Type Of Computer
$ChassisType = Get-WUChassisType
#Operating System
$OS = (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).caption
#System Installed On
$SystemInstalledOn = ((Get-CimInstance Win32_OperatingSystem -ComputerName $ComputerName).InstallDate).tostring("dd/MM/yyyy hh:mm:ss")
#Processor
$Processor = (Get-CimInstance win32_processor -ComputerName $ComputerName -filter "deviceid='CPU0'").Name
#Disk
$Disk = (Get-CimInstance Win32_LogicalDisk -ComputerName $ComputerName | Select-Object #{Name="Size"; Expression={"$([math]::round($_.Size / 1GB,2))GB"}}).Size
#Ram Memory
$Ram = (Get-CimInstance Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object #{Name="Capacity"; Expression={"$([math]::round($_.Capacity / 1GB,2))GB"}}).Capacity
#Serial Number
$SerialNumber = Get-CimInstance win32_bios -ComputerName $ComputerName | Select-Object -ExpandProperty SerialNumber
#Manufacturer
$Manufacturer = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Vendor
#Model
$Model = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Name
#Export to CSV
$Name + $LastLoggedUser + $ChassisType + $LastBoot + $OS + $SystemInstalledOn + $Processor + $Disk + $Ram + $SerialNumber + $Manufacturer + $Model | Export-Csv
#Dialog box to information finish script
[Microsoft.VisualBasic.Interaction]::MsgBox("Report is finished", "OKOnly,SystemModal,Information", "Success") | Out-Null```
edit: rewrote it a little for you, try the below.
Note that the input csv expects a header called "ComputerName" and a list of computer names underneath that.
I copied the csv-to-excel part at the bottom from here
#Function to let user select a file then return the filepath.
Function Get-FileName($initialDirectory){
[System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”) | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = “CSV Exports (*.csv)| *.csv”
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.FileName
}
$filepath = Get-FileName -initialDirectory $PSScriptRoot
$csvdata = Import-Csv -Path $filepath
#Function to create the Get-WUChassisType that is performed to find out if the Chassis of the equipment is Notebook or Desktop, and it is not configured to detect virtual machine
Function Get-WUChassisType {
[CmdletBinding()]
param ($ComputerName=$null)
Set-StrictMode -Version 'Latest'
$chassisTypes = Get-CimInstance Win32_SystemEnclosure -ComputerName $ComputerName | Select -ExpandProperty ChassisTypes
switch ($chassisTypes) {
{ $_ -in 3, 4, 5, 6, 7, 15, 16 } {
return 'Desktop'
}
{ $_ -in 8, 9, 10, 11, 12, 14, 18, 21, 31, 32 } {
return 'Notebook'
}
{ $_ -in 30 } {
return 'Tablet'
}
{ $_ -in 17, 23 } {
return 'Servidor'
}
Default {
return ''
}
}
}
#Function to get last logged user on remote computer
Function Get-LastUser($ComputerName){
try {
Get-WmiObject Win32_LoggedOnUser -ComputerName $ComputerName |
Select Antecedent -Unique |
% {
$domain = $_.Antecedent.Split('"')[1]
if($domain -eq "VLINET") {
"{0}\{1}" -f $domain, $_.Antecedent.Split('"')[3]
}
} | Select-Object -First 1
} catch [System.Runtime.InteropServices.COMException]{
}
}
#We'll append all our individual pc resuls into this array
$exportObj = #()
$offlineObj = #()
#Check computer online in network, if is not online, the hostname will be skipped but necessary add in log entry which computers is offline
foreach ($row in $csvdata){
if(Test-Connection $row.ComputerName -Count 1){
$ciminfo = Get-CimInstance win32_operatingsystem -ComputerName $row.ComputerName -ErrorAction SilentlyContinue |Select-Object CSName, LastBootUpTime, Caption, InstallDate
$sysinfo = Get-CimInstance win32_ComputerSystemProduct -ComputerName $row.ComputerName -ErrorAction SilentlyContinue | Select-Object Vendor, Name
if ($ciminfo -and $sysinfo){
$objPcResult = New-Object PSObject -Property #{
Name = 'Hostname: ' + $row.ComputerName;
ChassisType = Get-WUChassisType -ComputerName $row.ComputerName;
LastLoggedUser = ((Get-LastUser -ComputerName $row.ComputerName).Split('\')[1]);
LastBoot = $ciminfo.LastBootUpTime.tostring("dd/MM/yyyy hh:mm:ss");
OS = $ciminfo.Caption;
SystemInstalledOn = $ciminfo.InstallDate.tostring("dd/MM/yyyy hh:mm:ss");
Processor = (Get-CimInstance win32_processor -ComputerName $row.ComputerName -filter "deviceid='CPU0'").Name;
Disk = (Get-CimInstance Win32_LogicalDisk -ComputerName $row.ComputerName | Select-Object #{Name="Size"; Expression={"$([math]::round($_.Size / 1GB,2))GB"}}).Size;
Ram = (Get-CimInstance Win32_PhysicalMemory -ComputerName $row.ComputerName | Select-Object #{Name="Capacity"; Expression={"$([math]::round($_.Capacity / 1GB,2))GB"}}).Capacity;
SerialNumber = Get-CimInstance win32_bios -ComputerName $row.ComputerName | Select-Object -ExpandProperty SerialNumber;
Manufacturer = $sysinfo.Vendor;
Model = $sysinfo.Name;
}
#Add each PC results as a new row in our array
$exportObj += $objPcResult
}
}else{
$objPcResult = New-Object PSObject -Property #{
Name = 'Hostname: ' + $row.ComputerName;
}
$offlineObj += $objPcResult
}
}
#Setup our temp variables to save our collected data as a temporary CSV, so we can import it into Excel to save as an XLSX.
$offlinecsv = "c:\temp\offline.csv" #Location of offline hosts
$tempcsv = "c:\temp\temp.csv" #Location of the source file
$xlsx = "c:\temp\output.xlsx" #Desired location of output
$delimiter = "," #Specify the delimiter used in the file
#Temp export our csv - to be converted to xlsx
$exportObj | Export-Csv -Path $tempcsv -NoTypeInformation
$offlineObj | Export-Csv -Path $offlinecsv -NoTypeInformation
### Create a new Excel Workbook with one empty sheet
$excel = New-Object -ComObject excel.application
$excel.Visible = $false
$workbook = $excel.Workbooks.Add(1)
$worksheet = $workbook.worksheets.Item(1)
# Build the QueryTables.Add command and reformat the data
$TxtConnector = ("TEXT;" + $tempcsv)
$Connector = $worksheet.QueryTables.add($TxtConnector,$worksheet.Range("A1"))
$query = $worksheet.QueryTables.item($Connector.name)
$query.TextFileOtherDelimiter = $delimiter
$query.TextFileParseType = 1
$query.TextFileColumnDataTypes = ,1 * $worksheet.Cells.Columns.Count
$query.AdjustColumnWidth = 1
# Execute & delete the import query
$query.Refresh()
$query.Delete()
# Save & close the Workbook as XLSX.
$Workbook.SaveAs($xlsx,51)
$excel.Quit()
Here is an example of how I'd make an initial pass at changing your function with the primary changes being:
Multiple calls were being made to the same class, remotely. This is really inefficient. The calls were consolidated to store the result of the first call in a single variable and then reference the variable's properties for the additional information.
Your script is setting preferences, globally, a lot. All PowerShell cmdlets allow you to set the ErrorActionPreference inline, so setting it globally back and forth is unnecessary.
By prestaging an output object ($temp) and emitting it where the code quits processing, you can see the results of partial communications failures (e.g. pingable but WinRM off, etc.).
Your chassis function shouldn't have worked. Since chassisTypes returns an array, you can't use the -in operator to check if an array exists in an array. I used some code from another SO article that shows some pretty cool PowerShell on how to make this value report the chassis values accurately.
The CIM instance of Win32_LoggedOnUser returns the 'domain' and 'name' properties directly so you don't need to string-parse the values using splits and array references.
Examples of using it in the desired states you specified in the question are at the bottom (accepting multiple computer names and accepting them from the contents of a file (not with a popup, but I hope you'll see how not using GUIs will be more helpful)).
For the amount of independent remote calls you're making, if you identify that the code runs slower than you'd like, you might think about adapting it to use Invoke-Command. This would pass all of your code to the remote machine once, process it on the remote machine, and just return the output object. In my experience, this dramatically reduces the execution time of the script (e.g. I was able to pull information from thousands of servers spanning the globe in about 15 minutes using Invoke-Command, whereas individual remote calls took 12 hours or more).
Function Generate-ComputerHwReport {
param(
[Parameter(Mandatory=$true)] [string[]] [ValidateNotNullOrEmpty()] $ComputerNames ## This lines requires the passed in value to be an array of strings
)
## Modification -- Looping through the array to check all computers passed in
foreach ($ComputerName in $ComputerNames) {
$temp = [pscustomobject] #{
TestPing = $false
TestWinRM = $false
TestWSMan = $false
Hostname = $ComputerName
LastUser = ''
LastBootTime = ''
ChassisType = ''
OS = ''
InstallDate = ''
Processor = ''
Disk = ''
Ram = ''
SerialNumber = ''
Manufacturer = ''
Model = ''
ErrorLog = ''
}
#Check computer online in network, if is not online, the hostname will be skipped but necessary add in log entry which computers is offline
if (Test-Connection $ComputerName -Count 1 -Quiet) {
$temp.TestPing = $true
} else {
$temp
continue
}
#Validade crucial service that is crucial for get remote data, and if is not possible to get this information, the hostname will be skipped
try {
$Win32_OS = Get-CimInstance win32_operatingsystem -ComputerName $ComputerName -ErrorAction Stop
$temp.TestWinRM = $true
$temp.LastBootTime = $Win32_OS.LastBootUpTime.ToString("dd/MM/yyyy hh:mm:ss")
$temp.OS = $Win32_OS.Caption
$temp.InstallDate = $Win32_OS.InstallDate.ToString("dd/MM/yyyy hh:mm:ss")
} catch {
#Hostname will be skipped
$temp
continue
}
#Validate if the WS Management service is enabled on the remote device
if ((Test-NetConnection $ComputerName -Port 5985).TcpTestSucceeded) {
$temp.TestWSMan = $true
} else {
$temp
continue
}
#Function to create the Get-WUChassisType that is performed to find out if the Chassis of the equipment is Notebook or Desktop, and it is not configured to detect virtual machine
## https://stackoverflow.com/questions/55184682/powershell-getting-chassis-types-info
$ChassisTypes = #{
Name = 'ChassisType'
Expression = {
# property is an array, so process all values
$result = foreach($value in $_.ChassisTypes)
{
switch([int]$value)
{
1 {'Other'}
2 {'Unknown'}
3 {'Desktop'}
4 {'Low Profile Desktop'}
5 {'Pizza Box'}
6 {'Mini Tower'}
7 {'Tower'}
8 {'Portable'}
9 {'Laptop'}
10 {'Notebook'}
11 {'Hand Held'}
12 {'Docking Station'}
13 {'All in One'}
14 {'Sub Notebook'}
15 {'Space-Saving'}
16 {'Lunch Box'}
17 {'Main System Chassis'}
18 {'Expansion Chassis'}
19 {'SubChassis'}
20 {'Bus Expansion Chassis'}
21 {'Peripheral Chassis'}
22 {'Storage Chassis'}
23 {'Rack Mount Chassis'}
24 {'Sealed-Case PC'}
default {"$value"}
}
}
$result
}
}
$temp.ChassisType = (Get-CimInstance -ClassName Win32_SystemEnclosure -ComputerName $ComputerName | Select-Object -Property $ChassisTypes).ChassisType
#Function to get last logged user on remote computer
try {
$t = Get-CimInstance win32_loggedonuser -ComputerName $ComputerName -ErrorAction Stop | Select Antecedent -Unique
$temp.LastUser = "{0}\{1}" -f $t.Antecedent.Domain, $t.Antecedent.Name
} catch {
$temp.ErrorLog += $_
}
#Processor
$temp.Processor = (Get-CimInstance win32_processor -ComputerName $ComputerName -filter "deviceid='CPU0'").Name
#Disk
$temp.Disk = ((Get-CimInstance Win32_LogicalDisk -ComputerName $ComputerName | Select-Object #{Name="Size"; Expression={"$([math]::round($_.Size / 1GB,2))GB"}}).Size) -join ', '
#Ram Memory
$temp.Ram = ((Get-CimInstance Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object #{Name="Capacity"; Expression={"$([math]::round($_.Capacity / 1GB,2))GB"}}).Capacity) -join ', '
#Serial Number
$temp.SerialNumber = Get-CimInstance win32_bios -ComputerName $ComputerName | Select-Object -ExpandProperty SerialNumber
#Manufacturer
$temp.Manufacturer = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Vendor
#Model
$temp.Model = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Name
$temp
}
}
## Output to console
Generate-ComputerHwReport -ComputerNames localhost, pc2
## Output to console reading in the computer names from a file
Generate-ComputerHwReport -ComputerNames (gc listofcomputernames.txt)
## Output to CSV reading in the computer names from a file
Generate-ComputerHwReport -ComputerNames (gc listofcomputernames.txt) | Export-Csv -NoTypeInformation ComputerHwReport.csv
I am attempting to extract the date last modified from the files in a Windows directory. Here is my basic script:
Function Get-FolderItem {
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin {
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/E","/NJH","/NDL","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W","/TS","/UNILOG:c:\temp\test.txt"))
#params.AddRange(#("/L","/S","/NJH","/BYTES","/FP","/NC","/NDL","/TS","/XJ","/R:0","/W:0"))
If ($PSBoundParameters['MaxAge']) {
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge']) {
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process {
ForEach ($item in $Path) {
Try {
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile']) {
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
} Else {
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | Out-Null
get-content "c:\temp\test.txt" | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
$object = New-Object PSObject -Property #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = ([System.IO.FileInfo] $matches.FullName).creationtime
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object -ignorewhitespace -Character).Characters
Owner = (Get-ACL $matches.Fullname).Owner
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
$a = Get-FolderItem "C:\TargetDirectory\Folder" | Export-Csv -Path C:\Temp\output.csv -Encoding Unicode
The script extracts the date last modified of filepaths less than 260 characters. It returns a nonsense date of 1600-12-31 4:00:00 PM for files longer than 260 characters. Here is the line that is not working:
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
My first attempt to solve this problem was to find a command that began with Get- because such commands were useful in extracting filehashes, filepaths, character counts and owner names of files longer than 260 characters. For example:
Owner = (Get-ACL $matches.Fullname).Owner
Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object-ignorewhitespace -Character).Characters
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Get-Date however seemed to be about getting the current date.
In my second attempt, I went back to Boe Prox's original blogpost on this script and noticed that his script had two components that were missing from mine:
a robocopy switch /TS
Date = [datetime]$matches.Date
I added to my script however doing so return an error: WARNING: Cannot convert null to type "System.DateTime". I rechecked the file in the directory, and it clearly has a date.
I reexamined the documentation on Get-Date and tried
Date = Get-Date -Format o | ForEach-Object { $matches -replace ":", "." }
However, this returned WARNING: Cannot convert value "2018/03/05 18:06:54 C:TargetDirectory\Folder\Temp.csv to type "System.IO.FileInfo". Error: " Illegal characters in path."
(N.B. In other posts, people have suggested changing the server settings to permit the existence of files longer than 260 characters. This is not an option for me because I do not have access to the servers.)
Once you hit 260 characters in the path, you hit the old Windows MAX_PATH limitation. In order to get around that, you have to prepend your path with \\?\.
In your code above, you do that for Characters and FileHash but you don't do that when retrieving LastWriteTime. e.g. Changing the path to this will work:
Created = ([System.IO.FileInfo] "\\?\$($matches.FullName)").creationtime
LastWriteTime = ([System.IO.FileInfo] "\\?\$($matches.FullName)").LastWriteTime
The alternative way is to use the Get-ChildItem cmdlet along with \\?\ prepended to the path to retrieve most of the fields you want without having to query it multiple times:
get-content "c:\temp\test.txt" | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
$file = Get-ChildItem "\\?\$($matches.FullName)"
$object = New-Object PSObject -Property #{
FullName = $file.FullName
Extension = $file.Extension
FullPathLength = $file.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = $file.CreationTime
LastWriteTime = $file.LastWriteTime
Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object -ignorewhitespace -Character).Characters
Owner = (Get-ACL $matches.Fullname).Owner
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
I have a script that checks remote servers for tomcat and the associated java versions. It takes about 60 seconds to run against a list of about 16 servers. I'm just curious if the script is as efficient as realistically possible. I'm far from a PowerShell pro but I'm satisfied with the outcome. Just checking for where there is room for improvement.
$Servers = 'server1','server2','etc'
$Output = #()
foreach ($Server in $Servers)
{
$SName = gwmi -Class Win32_Service -ComputerName $Server -Filter {Name LIKE 'Tomcat%'}
IF ($SName -ne $null) {
$Output += [PSCustomObject]#{
Server_name = $SName.PSComputerName
Service_name = $SName.Name
Service_status = $SName.State
Tomcat_version = "$(Get-Content -Path ("\\"+$SName.PSComputerName+"\"+"$($SName.PathName.ToString())".Substring(0,$SName.Pathname.LastIndexOf("\")-3)+"\webapps\ROOT\RELEASE-NOTES.txt" -replace ":", "$") | Select-String -Pattern 'Apache Tomcat Version ')".TrimStart()
Java_Version = (Invoke-Command -ComputerName $Server -ScriptBlock {(GCI -Path "$((Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Apache Software Foundation\Procrun 2.0\Tomcat9\Parameters\Java').Jvm)").VersionInfo.ProductName})
}
}
Else {}
}
$Output | Select Server_name, Service_name,Service_status, Tomcat_Version, Java_Version | Format-Table -AutoSize
Can I simplify things anymore?
Is the time to completion decent for what is being performed?
Invoke-Command allows you to connect with multiple computers at the same time which should be more efficient.
+= is pretty bad, please read: Why should I avoid using the increase assignment operator (+=) to create a collection
You're querying WMI first and then if service is there you are using Invoke-Command, mind as well, connect once to the remote host and check everything.
I personally would do something like this
$Servers = 'server1','server2','etc'
$scriptBlock = {
# Since you're querying each server, and then if the service is there you Invoke-Command,
# mind as well Invoke-Command at first and if the service is there enter the If condition,
# else close the connection.
$tomcatServ = Get-CimInstance -Class Win32_Service -Filter "Name LIKE 'Tomcat%'"
if($tomcatServ)
{
##### This part is pretty confusing for someone reading your code, if you show us how does
##### RELEASE-NOTES.txt looks we may be able to improve it and simplified it a bit
$path = $tomcatServ.PathName.Substring(0,$tomcatServ.PathName.LastIndexOf("\")-3)
$path = Join-Path $path -ChildPath "webapps\ROOT\RELEASE-NOTES.txt"
$tomCatVer = ((Get-Content $path) -replace ":", "$" | Select-String -Pattern 'Apache Tomcat Version ').TrimStart()
##### This part is a bit confusing too
$key = 'HKLM:\SOFTWARE\WOW6432Node\Apache Software Foundation\Procrun 2.0\Tomcat9\Parameters\Java'
$javaVer = GetChild-Item -Path ((Get-ItemProperty -Path $key).Jvm).VersionInfo.ProductName
#####
[PSCustomObject]#{
Server_name = $env:ComputerName
Service_name = $tomcatServ.Name
Service_status = $tomcatServ.State
Tomcat_version = $tomCatVer
Java_Version = $javaVer
}
}
}
$Output = Invoke-Command -ComputerName $Servers -ScriptBlock $scriptBlock -HideComputerName
$Output | Select-Object * -ExcludeProperty RunspaceID | Format-Table -AutoSize
GOAL: Obtain a CSV file with the following information:
Computer Name
Share Name
Share Path
Share Description
for all non-admin (type 0) SMB shares on all servers from a list (txt file).
INITIAL CODE:
param (
[Parameter(Mandatory=$true,Position=0)]
[ValidateNotNullOrEmpty()]
[String]
$path
)
$computers = Get-Content $path
$shareInfo = #()
ForEach ($computer in $computers) {
$shares = gwmi -Computer $computer -Class Win32_Share -filter "Type = 0" | Select Name,Path,Description
$shares | % {
$ShareName = $_.Name
$Props = [ordered]#{
Computer = $computer
ShareName = $_.Name
Path = $shares.Path
Description = $shares.Description
}
}
$ShareInfo += New-Object -TypeName PSObject -Property $Props
}
$shareInfo | Export-CSV -Path .\shares.csv -NoType
CODE OUTPUT:
"Computer","ShareName","Path","Description"
"SERVER1","SHARE1","System.Object[]","System.Object[]"
"SERVER2","SHARE12","System.Object[]","System.Object[]"
"SERVER3","SHARE3","System.Object[]","System.Object[]"
PROBLEM:
While the code provides output for each server, it seems to not include all shares from the servers. Furthermore, the Path and Description fields are not populated with good information.
ADDITIONAL INFO:
The code:
$shares = gwmi -Computer $computer -Class Win32_Share -filter "Type = 0" | Select Name,Path,Description
Produces good information as below:
Name Path Description
---- ---- -----------
print$ C:\WINDOWS\system32\spool\drivers Printer Drivers
Share D:\Share
SHARE2 D:\SHARE2
Software C:\Software The Software
$shares | % {
$ShareName = $_.Name
$Props = [ordered]#{
Computer = $computer
ShareName = $_.Name
Path = $shares.Path
Description = $shares.Description
}
}
You're using $shares instead of $_ for the Path and Description properties, so each of these properties is assigned a list of the values of the respective property of each element of the $shares collection.
Also, why are you building custom objects in the first place when you just need to filter the WMI query results? The computer name can be obtained from the __SERVER (or PSMachineName) property. Plus, type 0 means a shared disk drive, not an administrative share. You need to filter the latter by other criteria (usually description and/or share name).
$filter = "Type = 0 And Description != 'Default Share' And " +
"Name != 'ADMIN$' And Name != 'IPC$'"
$computers |
ForEach-Object { Get-WmiObject -Computer $_ -Class Win32_Share -Filter $filter } |
Select-Object #{n='Computer';e={$_.__SERVER}}, Name, Path, Description |
Export-Csv -Path .\shares.csv -NoType
I have this code that generates a list of all the shares and the size however can not generate a txt with this information or the shared location
$servers = #("servername")
$sizes = #()
foreach($server in $servers) {
write-host "Server: $server"
(gwmi -class Win32_Share -ComputerName $server -filter "Type = 0" |
% {
write-host " share: $($_.Name)"
$s = gci \\$server\$($_.Name) -recurse -force | Measure-Object -Property length -Sum
New-Object PSObject -property #{Name=$_.Name; Server=$server; TotalSize=$s.Sum }
})
}
And this not only shows me the size and generates txt size and can generate txt
Get-WmiObject Win32_share -computer server01 | FT "server01", path, name > ServerShares.txt
Get-WmiObject Win32_share -computer server02 | FT "server02", path, name >> ServerShares.txt
Someone could help me to create only one that does everything
In your New-Object you just need to add additional properties to get the information you want:
If you're not running PowerShell v3, remove [Ordered]
$servers = #("servername")
$sizes = #()
foreach($server in $servers)
{
write-host "Server: $server"
# Get all shares
$shares = Get-WmiObject -class Win32_Share -ComputerName $server -filter "Type = 0"
# go through each share
foreach($share in $shares)
{
write-host " share: $($share.Name)"
# Get size of share
$size = Get-ChildItem -Path "\\$server\$($_.Name)" -recurse -force | Measure-Object -Property length -Sum
# Create a new object to store information
New-Object PSObject -property ([ordered]#{
# Name of share
Name = $share.Name
# Share path
Path = $share.path
# What server share is on
Server = $server
# Total size of share
TotalSize = $size.Sum
# Change this path to where you want the file to be saved to
}) | Export-Csv -Path C:\ShareDetails.csv -NoTypeInformation -Append
}
}
I made a small revision to #Bluecakes response in order to use COM instead of .NET to capture the size information. This overcomes the path-length issues.
# Get size of share
# $size = Get-ChildItem -Path "$($share.Name)" -recurse -force | Measure-Object -Property length -Sum
$objFSO = New-Object -com Scripting.FileSystemObject
$size = "{0:N2}" -f (($objFSO.GetFolder("$($share.Name)").Size) / 1MB)
Then you also need to remove ".sum"
# Total size of share
TotalSize = $size