The task is to track the execution of SQL backups. One of the tracking mechanisms is to browse the directory where the backups are saved and if the file is missing, you can send an email with the full address of the directory where the file is missing. This is what I am trying to implement with Powershell script, but for some reason my script doesn't work and doesn't give me the information I need.
$list = Get-ChildItem -Path network_path_to_share -Recurse -File | Where-Object {$_.DirectoryName -like '*FULL*' -and $_.LastWriteTime -ge (Get-Date).AddDays(-6)}
$fileExists = Test-Path $list
If ($fileExists)
{
#Do Nothing
}
Else
{
$list | Select DirectoryName
}
Can anyone help?
I suppose what you need is to test each file or path individually. You take Get-ChildItem with recurse, so it returns multiple files and stores them in $list.
If you do something like
Foreach ($item in $list) {
$fileexists = Test-Path $item
If ($fileexists -eq $false) {
do something }
}
You should be good to go. This would cycle through all items and does whatever you need to be done. If you compare against $false, you wouldn't need the else statement, and you could also just put "Test-Path" into the if-statement like
If (Test-Path $item -eq $false) {}
Edit: Sorry I accidentally posted the answer before finishing it lol
Also, as stackprotector correctly points out, Get-ChildItem can only retrieve items that exist, because how should it detect missing files.
If you're wanting to check for something that is missing or doesn't exist, you need to start with a known condition, e.g.: either the server names or expected file or directory names.
If you know that, then you can create a static list (or dynamically query a list from Active Directory for your SQL servers or something (assuming the backup file names correspond to the server names)) and then check the files that were created and output the missing ones for triage.
Here is a modification to your script (essentially the opposite of what you did) that might point you in the right direction:
## List of expected files
$ExpectedFiles = #(
'File1.bak',
'File2.bak',
'File3.bak',
'File4.bak'
'...'
)
## Get a list of created files
$list = Get-ChildItem -Path network_path_to_share -Recurse -File | Where-Object {$_.DirectoryName -like '*FULL*' -and $_.LastWriteTime -ge (Get-Date).AddDays(-6)} | Select -ExpandProperty Name
## Check whether each expected file exists in the array of backups that actually were created
foreach ($file in $ExpectedFiles) {
if (-not(Test-Path $list)) {
"$($file) is missing!"
}
}
I am trying to search all user's desktops for a particular shortcut and I find difficulties enumerate all desktop paths for different users on the computer as some of them have OneDrive sync and the standard path c:\Users\%user%\Desktop is not to be found.
I have tried getting the path with the GetFolderPath which only returns the path to the current user:
[System.Environment]::GetFolderPath("Desktop")
So briefly the path scenarios are:
C:\users\username\Desktop
C:\users\username\One Drive - Company\Desktop
I would be glad if somebody has a hint how to find all paths in this mixed environment.
Here's my older training script. I don't know how (or if any) it works with OneDrive cync as I have OneDrive disabled (or even uninstalled) because I found it extremely irritating…
Remove-Variable path -ErrorAction SilentlyContinue
Write-Verbose "--- Special Folders ---" -Verbose
$SpecialFolders = #{}
$names = [Environment+SpecialFolder]::GetNames( [Environment+SpecialFolder])
ForEach ($name in $names) {
# assign and then check
if( $path = [Environment]::GetFolderPath($name) ){
$SpecialFolders[$name] = $path
} else {
Write-Warning $name
$SpecialFolders[$name] = ''
}
}
$SpecialFolders.GetEnumerator() |
Sort-Object -Property name #| Format-Table -AutoSize
"---"
###Pause
$ShellFolders=#{}
Write-Verbose "--- Shell Folders ---" -Verbose
[System.Enum]::GetValues([System.Environment+SpecialFolder]) |
ForEach-Object {
$ShellFolders[$_.ToString()] =
($_.value__, [System.Environment]::GetFolderPath($_))
}
$ShellFolders.GetEnumerator() |
Sort-Object -Property name # | Format-Table -AutoSize
I'm fairly close to a solution, but I just can't get there. What I'm trying to do is search for installed MS Office updates. The best way I've found is to search the HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall key.
What I then want to do is only look at sub-keys like *{90140000-001* (which indicates Office) and the search each found sub-key's DisplayName property for "(KB*" - which will indicate it is an update to Office rather than a component.
What I have so far is this:
$ErrorActionPreference = "SilentlyContinue"
Get-ChildItem "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\" -Recurse | Where-Object{$_ -like "*{90140000-001*"} | foreach {
Get-ItemProperty $_.DisplayName}
But it produces blank output.
Is anyone able to please help me finish this off?
You can use the GetValue method of the current RegistryKey to retrieve the DisplayName:
Get-ChildItem "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\" -Recurse |
Where Name -like '*{90140000-001*' | foreach {
$_.GetValue("DisplayName")
}
If you want to query remote computers:
#config
$computerName = $env:COMPUTERNAME
$hive = "LocalMachine"
#32-bit Office : SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
#64-bit Office : SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
#Office 2013 : look for keys named "{90140000-001*"
#Office 2016 : look for keys named "{90160000-001*"
$regPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"
#if remote computer is reachable
if(Test-Connection $computerName -Quiet -Count 2 -ErrorAction 0) {
try {
#open remote registry
$base = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::$hive, $ComputerName)
#open desired key with edit permission
$key = $base.OpenSubKey($regPath)
foreach($subkeyName in $key.GetSubKeyNames()) {
if($subkeyName -match "{90160000-001") {
$subkey = $key.OpenSubKey($subkeyName)
$displayName = $subkey.GetValue("DisplayName")
if($displayName -match "\(KB") {
$displayName
}
}
}
#close subkey, key and registry connection
$subkey.Close()
$key.Close()
$base.Close()
} catch {
Throw "Remote registry is not accessible (check `$hive and `$regPath, and run this script as administrator)."
}
} else {
Throw "Remote computer is not reachable."
}
I am trying to figure out a script that'll help me list all the Microsoft updates installed on my system.
I am using
Get-Hotfix
to do the same but i am not getting desired results. Neither is:
Get-WmiObject -Class "win32_quickfixengineering" |
where $_.name = "Microsoft"
this working for me.
Please help.
You could use this script (didn't find a way to display the description with Get-HotFix).
It lists the programs found in the Uninstall keys of the windows registry, and matches the name againt the $filter string.
You can remotely get this information from another computer by changing $computerName (currently the local host).
#store computer name in a variable
$computerName = $env:COMPUTERNAME
#store filter string in a variable
$filter = "KB"
#declare results array
$result = #()
#store registry key paths in an array
$keyList = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\',
'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\'
#open registry hive HKLM
$hive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $computerName)
#for each key path
foreach($key in $keyList) {
#open key
$uninstallKey = $hive.OpenSubKey($key)
#if key has been opened
if($uninstallKey) {
#list program keys
$programList = $uninstallKey.GetSubKeyNames()
#for each key
foreach($program in $programList) {
#get the program name
$programName = $uninstallKey.OpenSubKey($program).GetValue("DisplayName")
#if the program name is not null and matches our filter
if(($programName) -and ($programName -like "*$filter*")) {
#add program name to results array
$result += $programName
}
}
}
}
#sort and output results array
$result | Sort-Object
This could be a simple question, but I couldn't find a proper answer.
How do you update the environment variables from within a current Windows PowerShell session without closing the current one?
Up to now, when I modify for example the PATH environment variable from Control Panel > System, I have to close current session and open a new one, so that variables are refreshed, or issue a SetEnviromentVariable which is cumbersome.
I'm coming from the Linux world, so I'm looking for something like source command.
There's no need to go dumpster diving in the registry for this stuff:
foreach($level in "Machine","User") {
[Environment]::GetEnvironmentVariables($level)
}
If you want to make PATH variables work right, you'd have to treat them specially (that is, concatenate the (potentially) new stuff to what you already have, and not lose anything, because you can't tell for sure what you should remove).
foreach($level in "Machine","User") {
[Environment]::GetEnvironmentVariables($level).GetEnumerator() | % {
# For Path variables, append the new values, if they're not already in there
if($_.Name -match 'Path$') {
$_.Value = ($((Get-Content "Env:$($_.Name)") + ";$($_.Value)") -split ';' | Select -unique) -join ';'
}
$_
} | Set-Content -Path { "Env:$($_.Name)" }
}
Notice that Set-Content actually sets the variables in your process environment, the same as doing something like $env:Temp = Convert-Path ~\AppData\Local\Temp
The environment gets populated on process start-up which is why you'll need to restart the process.
You can update the environment by reading it from the registry. Maybe with a script like the following:
function Update-Environment {
$locations = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'HKCU:\Environment'
$locations | ForEach-Object {
$k = Get-Item $_
$k.GetValueNames() | ForEach-Object {
$name = $_
$value = $k.GetValue($_)
if ($userLocation -and $name -ieq 'PATH') {
Env:\Path += ";$value"
} else {
Set-Item -Path Env:\$name -Value $value
}
}
$userLocation = $true
}
}
(untested)
This is an old question, but if you use chocolatey, it also installs the handy refreshenv command, which works fine for me.
If you want to update the Path for your current PowerShell session only, do this:
$env:Path += ";<new path>"
If you need to update the PATH env variable so that it is persistent across new PowerShell sessions use:
[Environment]::SetEnvironmentVariable("PATH", $env:Path + ";<new path>", 'User')
If you want to change the path for all users, then change 'User' to 'Machine'.
Thank you #Joey for your answer (I marked that correct :) ) Just to note that, given I want to refresh all variables according to the registry in that moment, I got rid of the if-else clause...
I double checked the registry variable location and are ok according to Where are environment variables stored in registry?
Snippet used is here just for completeness....
function Update-Environment {
$locations = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'HKCU:\Environment'
$locations | ForEach-Object {
$k = Get-Item $_
$k.GetValueNames() | ForEach-Object {
$name = $_
$value = $k.GetValue($_)
Set-Item -Path Env:\$name -Value $value
}
}
}
Unfortunately the other answers are missing some important details. PSModulePath has some special handling in PowerShell that needs to be considered when updating environment variables. Also, variables may be added, modified, or removed. And you need to take path environment variables into consideration.
To make it worse, depending on what product/host you are in when you do this, that product may have some special environment variable handling of its own that should be considered, and aside from hoping that Path environment variables (those that contain multiple values, semi-colon delimited) actually contain "Path" as part of their name, you may miss out on something (unless of course this is how Windows handles those environment variables internally, which could be, I didn't dig that far down the rabbit hole).
Here is a script that I came up with that is about as close as I think you'll get to properly updating environment variables in your current PowerShell session without restarting it:
# Get all environment variables for the current Process, as well as System and User environment variable values
$processValues = [Environment]::GetEnvironmentVariables('Process')
$machineValues = [Environment]::GetEnvironmentVariables('Machine')
$userValues = [Environment]::GetEnvironmentVariables('User')
# Identify the entire list of environment variable names first
$envVarNames = ($machineValues.Keys + $userValues.Keys + 'PSModulePath') | Sort-Object | Select-Object -Unique
# Now process all of those keys, updating what exists and adding what is new
foreach ($envVarName in $envVarNames) {
if ($envVarName -eq 'PSModulePath') {
$pieces = #()
if ($PSVersionTable.PSVersion -ge [System.Version]'4.0') {
$pieces += Join-Path -Path ${env:ProgramFiles} -ChildPath 'WindowsPowerShell\Modules'
}
if (-not $userValues.ContainsKey($envVarName)) {
$pieces += Join-Path -Path ([Environment]::GetFolderPath('Documents')) -ChildPath 'WindowsPowerShell\Modules'
} else {
$pieces += $userValues[$envVarName] -split ';'
}
if ($machineValues.ContainsKey($envVarName)) {
$pieces += $machineValues[$envVarName] -split ';'
}
[Environment]::SetEnvironmentVariable($envVarName,($pieces -join ';'),'Process')
} elseif ($envVarName -match 'path') {
$pieces = #()
if ($userValues.ContainsKey($envVarName)) {
$pieces += $userValues[$envVarName] -split ';'
}
if ($machineValues.ContainsKey($envVarName)) {
$pieces += $machineValues[$envVarName] -split ';'
}
[Environment]::SetEnvironmentVariable($envVarName,($pieces -join ';'),'Process')
} elseif ($userValues.ContainsKey($envVarName)) {
[Environment]::SetEnvironmentVariable($envVarName,$userValue[$envVarName],'Process')
} elseif ($machineValues.ContainsKey($envVarName)) {
[Environment]::SetEnvironmentVariable($envVarName,$machineValue[$envVarName],'Process')
}
}
# Lastly remove the environment variables that no longer exist
foreach ($envVarName in $processValues.Keys | Where-Object {$envVarNames -notcontains $_}) {
Remove-Item -LiteralPath "env:${envVarName}"
}
Note that this is largely untested however the principle is sound and it is based on work I have done in the past in this area.