Powershell search path with wildcard-string-wildcard - powershell

I am trying to search a log file for updates that were not installed, then using the returned array install the updates. Problem is my files are named:
Windows6.1-KB3102429-v2-x64.msu
My parsed array has a item of KB3102429 how can I use a wild card - call the array item - then another wildcard .msu
my code is listed below:
# Read KBLIST.txt and create array of KB Updates that were not installed
$Failed = Get-Content -Path C:/Updates/KBLIST.txt | Where-Object {$_ -like '*NOT*'}
# create a list of all items in Updates folder
$dir = (Get-Item -Path "C:\Updates" -Verbose).FullName
# Parse the $Failed array down to just the KB#######
for($i = $Failed.GetLowerBound(0); $i -le $Failed.GetUpperBound(0); $i++)
{
$Failed[$i][1..9] -join ""
# Search the $dir list for files that contain KB####### and end in .msu then quiet install
Foreach($item in (ls $dir *$Failed[$i]*.msu -Name))
{
echo $item
$item = "C:\Updates\" + $item
wusa $item /quiet /norestart | Out-Null
}
}
It works down to the Foreach($item in (ls $dir *$Failed[$i]*.msu -Name)).
If I just use * instead of the wildcard,string,wildcard it returns a list of all the .msu files for the basic syntax it correct.

It was hard to follow your work since you used aliases, but I think this should be able to accomplish what you're looking for.
$UpdateFolder = 'C:\Updates'
$FailedUpdates = Get-Content -Path C:/Updates/KBLIST.txt | Where-Object {$_ -like '*NOT*'}
foreach ( $Update in $FailedUpdates )
{
Write-Host -Object "Update $Update failed"
$UpdatePath = Get-Item -Path "$UpdateFolder\*$Update*.msu" | Select-Object -ExpandProperty FullName
Write-Host -Object "`tReinstalling from path: $UpdatePath"
wusa $UpdatePath /quiet /norestart | Out-Null
}

Related

Get location of specific SCCM device collection in Powershell

I am writing a script to export the names of all computer in a device collection to a txt file. My script works as expected but I would like to preserve the folder structure in the exported file structure. For this I need to get the location of the Device Collection.
My Question:
Is there a way to get the location of a SCCM Device Collection in PowerShell?
I've stumbled across a few posts like this and this that use WMI and WQL for this, but I wasn't able to get those working in my script and I would like to do everything in PowerShell whenever possible.
$collections = (Get-CMDeviceCollection | Select -ExpandProperty "Name")
$totalCollections = $collections.length
"Number of Collections: $totalCollections"
$i = 0
foreach($name in $collections){
ForEach-Object -Process {
$i++
"Writing File $i of $totalCollections"
$SanitizedName = $name -replace '/','(slash)' -replace '\\','(backslash)' -replace ':','(colon)' -replace '\*','(asterisk)' -replace '\?','(questionmark)' -replace '"','(quote)' -replace '<','(less)' -replace '>','(more)' -replace '\|','(pipe)'
$file = New-Item -Path "C:\Temp\exporte\$SanitizedName.txt"
Add-Content -Path $file.FullName -Value (Get-CMCollectionMember -CollectionName $name | Select -ExpandProperty "Name")
}
}
I would like to expand this code so that the txt files are placed in the corresponding subfolder analog to the SCCM file structure. E.g rootFolder/rooms/
I was using this module until now but wasn't able to find anything that gives me back the specific location of a collection.
Thanks in advance
I wasn't able to find a way to do this in plain PowerShell and the SCCM Module. In the end I did it like #FoxDeploy suggested. I made a SQL query select for each collection (performance isn't an issue in my case) on our SCCM database to get the folder path. I then used this to place the export file in the appropriate place.
This is my working example with some confidential lines removed
## Parameter ##
$exportLocation = [removed]
$sqlServer = [removed]
$db = [removed]
$query = "SELECT [ObjectPath] FROM [removed].[v_Collections] WHERE CollectionName ="
$SiteCode = [removed] # Site code
$ProviderMachineName = [removed] # SMS Provider machine name
# Customizations
$initParams = #{}
# Import the ConfigurationManager.psd1 module
if((Get-Module ConfigurationManager) -eq $null) {
Import-Module [removed]\..\ConfigurationManager.psd1" #initParams
}
# Connect to the site's drive if it is not already present
if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName #initParams
}
Set-Location "$($SiteCode):\" #initParams
# get all collections and save them to an array
$collections = (Get-CMDeviceCollection | Select -ExpandProperty "Name")
# total number of collections
$totalCollections = $collections.length
# output to console
"Number of Collections: $totalCollections"
# empty output directory
Set-Location [removed]
Remove-Item $exportLocation\* -Recurse -Force
Set-Location [removed]
# loop through all collections
$i = 0
foreach($name in $collections){
ForEach-Object -Process {
# print progress
$i++
"Writing File $i of $totalCollections"
# remove all characters, that aren't compatible with the windows file naming scheme (/\:*?"<>|)
$SanitizedName = $name -replace '/','(slash)' -replace '\\','(backslash)' -replace ':','(colon)' -replace '\*','(asterisk)' -replace '\?','(questionmark)' -replace '"','(quote)' -replace '<','(less)' -replace '>','(more)' -replace '\|','(pipe)'
# get members of collection
$collectionMembers = (Get-CMCollectionMember -CollectionName $name | Select -ExpandProperty "Name")
# write to file
Set-Location [removed]
$path = (Invoke-Sqlcmd -ServerInstance $sqlServer -Database $db -Query "$query '$collection'").Item("ObjectPath")
New-Item -ItemType Directory -Force -Path "$exportLocation$path"
$file = New-Item -Path "$exportLocation$path\$SanitizedName.txt"
Add-Content -Path $file.FullName -Value $collectionMembers
Set-Location [removed]
}
}
hope this helps someone. Thanks #FoxDeploy

Execute get-childitem, but iterate in reverse order?

I have a folder full of 500,00+ files. I'm trying to iterate through this folder and run some logic to determine if we can delete unneeded files. The problem is this process needs to run semi-regularly and the new files that need to be deleted are currently at the end of the list it seems.
I put together the following list of code to sort through it all:
gci $RPT | %{
$flag = 0;
$number = [int]($_.Name | select-string -pattern "\d{12}" -Allmatches).Matches.Value
if ($submidlist -match "^$number$"){
if ($_ -notmatch "acct\.csv|jpd\.csv|jss\.pdf|jman\.pdf|3600\.pdf|cont\.pdf|msl\.txt|pres\.pdf|tray\.pdf|qual\.pdf|zipl\.pdf"){
echo "DELETE SUBMID $_"
remove-item $RPT\$_
$count++
$totalcount++
$flag = 1;
}
}
if ($jobidlist -match "^$number$"){
if ($_ -match "acct\.csv|jpd\.csv|jss\.pdf|jman\.pdf|3600\.pdf|cont\.pdf|msl\.txt|pres\.pdf|tray\.pdf|qual\.pdf|zipl\.pdf"){
echo "DELETE JOBID $_"
remove-item $RPT\$_
$count++
$totalcount++
$flag = 1;
}
}
}
Currently, running the above script takes over 24 hours and it still doesn't make it to the end of the list. Is there a way to optimize this or reverse the order that get-childitem iterates through this folder?
function Delete-Items($List, [string]$ListName){
$DoNotDelete = #("acct.csv","jpd.csv","jss.pdf","jman.pdf","3600.pdf","cont.pdf","msl.txt","pres.pdf","tray.pdf","qual.pdf","zipl.pdf")
$List = $List | %{
"*$_*"
}
Get-ChildItem C:\TEST\56381643\ -Recurse -Include $List -Directory | %{
Get-ChildItem $_.FullName -Exclude $DoNotDelete -Recurse | %{
echo "DELETE $ListName $($_.name | select-string -pattern "\d{12}")"
Remove-Item -Path $_.FullName -WhatIf
}
}
}
#Example Usage
$JobList = #(
098765432109
123456789012
)
$SubmitList = #(
234567890123
)
Delete-Items -List $JobList -ListName JOBID
Delete-Items -List $SubmitList -ListName SUBMID
Lets go over a basic rundown of whats happening in the function.
We have a array of files not to delete
We turn the $list numbers into wildcards by adding a * before and after each item in the array. We then only search for those directories that contain those numbers.
We then use another Get-ChildItem to get the files in each directory but exclude the ones mentioned in$DoNotDelete`.
If you want to delete the files delete the -Whatif on the remove-item

How to add headers to csv file

I am creating a script to get the file version of the dlls and export the output to a csv file , but I want to give particular headers to csv file. How should I do it ? I also need to add a new system date and time column to the csv file.
$j = 'C:\Program Files (x86)\anyfilepatha\'
$files = get-childitem $j -recurse -Include *.dll
$cvsdataFile = 'C:\Program Files\MySQL\Connector ODBC 8.0\dllsinfo.csv'
# Add-Content -Path "C:\Program Files\MySQL\Connector ODBC 8.0\dllsinfo.csv" -Value "Dlls" , "Version Name" , "Location"
$header = foreach ($i in $files)
{
if($i.Name -like '*Eclipsys*' -or $i.Name -like '*Helios*')
{
continue;
}
$verison = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($i).FileVersion
if($verison -eq $null)
{
$i.FullName | Out-File NoVersion.txt -append
}
else
{
Write-Host $i ----> $verison
"{0}`t{1}`t{2} " -f $i, [System.Diagnostics.FileVersionInfo]:: GetVersionInfo($i).FileVersion, $i.CreationTime, $i.FullName | Out-File -Append $cvsdataFile
}
}
You could do it with a one liner. You can exclude items with -Exclude parameter and PowerShell calculated properties for adding new columns. See more about calculated properties here.
Get-ChildItem -Path $j -File -Exclude '*Eclipsys*','*Helios*' -Include *.dll |
Select-Object -Property #{E={$_.versioninfo.Fileversion};l='Version'},#{E={Get-Date -f 'dd/mm/yyyy'};l='Date'},#{E={Get-Date -f 'hh:mm:ss ttt='};L='Time'}

Identify Empty Folders

I would like to identify a specific empty folder in our user profiles.
I have a text file containing all of our user names that I want the script to refer to. The script will loop each user directory and either output to file or screen and say if the directory is empty. Hidden files do not have to count!
Something similar
FOR /F %U IN (C:\UserList\UserList.TXT) DO *Find and List Empty Folder* \\Server\Share\%U\Target_Folder
Powershell solutions welcome!
This article on Technet provides the following Powershell code snippet to identify all empty folders:
$a = Get-ChildItem C:\Scripts -recurse | Where-Object {$_.PSIsContainer -eq $True}
$a | Where-Object {$_.GetFiles().Count -eq 0} | Select-Object FullName
Replace "C:\Scripts" with the root folder you want to search.
Update:
The following script will get you in the ballpark.
$content = Get-Content C:\Temp\FolderList.txt
foreach ($line in $content)
{
Write-Host $line -NoNewline
$testObject = Test-Path -Path $line
if ($testObject)
{
$folder = Get-Item -Path $line
$filesCount = $folder.GetFiles().Count
if ($filesCount.Equals(0))
{
Write-Host " - Empty folder"
}
else
{
Write-Host " - Contains files"
}
}
else
{
Write-Host " - Invalid path"
}
}

Only recurse subfolder x numbers of levels using PowerShell

The PowerShell script below will list out all shared folders (excluding hidden shared folders), then list out all sub-folders and finally get the ACL information of each of them and export to a CSV file.
However, I'm trying to set the limit of the sub-folder it can drill into. For example, if I set it to 3, the script will get the ACL information of first three sub-folders. How can I do this?
Input:
path=\\server\sharefolder0\subfolder01\subfolder02
path=\\server\sharefolder1\subfolder11\subfolder12\subfolder13\subfolder14
path=\\server\sharefolder2
Expected result:
path=\\server\sharefolder0
path=\\server\sharefolder0\subfolder01
path=\\server\sharefolder0\subfolder01\subfolder02
path=\\server\sharefolder1
path=\\server\sharefolder1\subfolder11
path=\\server\sharefolder1\subfolder11\subfolder12
path=\\server\sharefolder2
This is the code:
$getSRVlist = Get-Content .\server.txt
$outputDirPath=".\DirPathList.txt"
$outputACLInfo=".\ACLInfo.CSV"
$header="FolderPath,IdentityReference,Rights"
Del $outputACLInfo
add-content -value $header -path $outputACLInfo
foreach ($readSRVlist in $getSRVlist)
{
foreach ($readShareInfoList in $getShareInfoList=Get-WmiObject Win32_Share
-computerName $readSRVlist | Where {$_.name -notlike "*$"} | %{$_.Name})
{
foreach ($readDirPathList in
$getDirPathList=get-childitem \\$readSRVlist\$readShareInfoList -recurse
| where {$_.PSIsContainer})# | %{$_.fullname})
{
$getACLList=get-ACL $readDirPathList.fullname | ForEach-Object
{$_.Access}
foreach ($readACLList in $getACLList)
{
$a = $readDirPathList.fullname + "," +
$readACLList.IdentityReference + "," + $readACLList.FileSystemRights
add-content -value $a -path $outputACLInfo
}
}
}
}
Recursion is your friend. Try this:
$maxDepth = 3
function TraverseFolders($folder, $remainingDepth) {
Get-ChildItem $folder | Where-Object { $_.PSIsContainer } | ForEach-Object {
if ($remainingDepth -gt 1) {
TraverseFolders $_.FullName ($remainingDepth - 1)
}
}
}
TraverseFolders "C:\BASE\PATH" $maxDepth
Edit: Now I see what you mean. For checking the first three parent folders of a given path try this:
$server = "\\server\"
$path = ($args[0] -replace [regex]::escape($server), "").Split("\\")[0..2]
for ($i = 0; $i -lt $path.Length; $i++) {
Get-ACL ($server + [string]::join("\", $path[0..$i])
}
In newer version of powershell one can use -DEPTH parameter,
One liner can help-
get-childitem -path \\server\folder -Depth 2 -Directory | Select-object -Property Name, Fullname
It will search for 2 nested folders and will provide folder name and full path of that particular folder. Tested in version- PSVersion 5.1.17134.858