Trying to put together a script for automatic timezone updates - powershell

I am working on a powershell script and it isn't reading my "If, elseIf" properly so I know I am doing something wrong. I need some help figuring this out.
I start with pulling the Default Gateway and storing it:
$Gateway = (Get-wmiObject Win32_networkAdapterConfiguration | ?{$_.IPEnabled}).DefaultIPGateway
Next I am trying to get it to sort that so that if it equals one of my designated Default Gateways it will update the timezone.
If ($Gateway = "10.100.4.1")
{
$TZ = "Central Standard Time"
}
ElseIf ($Gateway = "10.101.4.1")
{
$TZ = "Central Standard Time"
}
and I am finishing it up with a
Set-TimeZone $TZ
The purpose is that if I image a system at the home office, and I ship it to "remote location" I can't trust an end user to update their time zone, and I have POS that was poorly written so that it doesn't use UTC/GMT, and can cause issues with the BOH systems et cetera.
I will be placing this in as a startup item to execute whenever the system starts up to ensure that it is always up to date with the TZ.
Changing the Win 10 to use an automatic update for the TZ doesn't work because reasons (read: Networking Team and Security Team are out to get me and in this instance it isn't paranoia).
So, where can I find help to put this all together?
Edit: I had a typo which is why it wasn't working. So... nevermind. For those ofyou interested in the typo, I removed it already. it was in my $Gateway portion, adding a " after {$_.IPEnabled} and before the )

Your "if" statements are using the assignment operator = instead of the equality operator, -eq.
Switch it to If ($Gateway -eq "10.100.4.1") and it should work.
P.S. I missed the typo you mentioned, but the assignment operator is still an issue. When the assignment operator is used in an "if/elsif" statment it will always return $true which would be rather problematic.

I would suggest using a lookup Hashtable for the designated default gateways.
Because you want this to be run as startup script, you also need to create a centralized path where errors can be logged or write errors in the windows eventlog.
Create a Hashtable with your designated gateway IP addresses as key and the timezone ids as value.
Either directly in code:
$timeZones = #{
'10.100.4.1' = 'Central Standard Time'
'10.101.4.1' = 'Central Standard Time'
'10.102.4.1' = 'Eastern Standard Time'
# etc.
}
Or by reading a centralized CSV file with columns 'IPAddress' and 'TimeZone'
$defaultTz = Import-Csv -LiteralPath '\\Server\Share\Folder\Timezones.csv'
$timeZones = #{}
$defaultTz | ForEach-Object {
$timeZones[$_.IPAddress] = $_.TimeZone
}
Next, use these values something like this (demo uses a centralized error log file):
$errorlog = '\\Server\Share\Folder\TimezonesErrors.log'
$now = (Get-Date).ToString() # use default format or specify the date format yourself here
$currentTz = (Get-TimeZone).Id # get the current timezone Id
# get the default gateway IPv4 address for this computer
$gw = #((Get-wmiObject Win32_networkAdapterConfiguration | Where-Object {$_.IPEnabled -and $_.DefaultIPGateway-like '*.*.*.*'}).DefaultIPGateway)[0]
# check if the gateway IP address is present in the lookup Hashtable
if ($timeZones.ContainsKey($gw)) {
$tz = $timeZones[$gw]
# only set the wanted timezone if it differs from the current timezone
if ($tz -ne $currentTz) {
try {
Set-TimeZone -Id $tz -ErrorAction Stop
}
catch {
# add the exception to the error log
$msg = "$now - Error setting the timezone on computer $($env:COMPUTERNAME): $_.Exception.Message"
Add-Content -LiteralPath $errorlog -Value $msg
}
}
}
else {
# make it known that the IP address for this gateway was not found in the Hashtable
$msg = "$now - No timezone found for default gateway $gw on computer $($env:COMPUTERNAME)"
# write error to the central error.log file
Add-Content -LiteralPath $errorlog -Value $msg
}

Related

Parse MDT Log using PowerShell

I am trying to setup a log which would pull different information from another log file to log assets build by MDT using PowerShell. I can extract a line of log using simple get-content | select-string to get the lines i need so output looks like that
[LOG[Validate Domain Credentials [domain\user]]LOG]!
time="16:55:42.000+000" date="10-20-2017" component="Wizard"
context="" type="1" thread="" file="Wizard"
and I am curious if there is a way of capturing things like domain\user, time and date in a separate variables so it can be later passed with another data captured in a similar way in output file in a single line.
This is how you could do it:
$line = Get-Content "<your_log_path>" | Select-String "Validate Domain Credentials" | select -First 1
$regex = '\[(?<domain>[^\\[]+)\\(?<user>[^]]+)\].*time="(?<time>[^"]*)".*date="(?<date>[^"]*)".*component="(?<component>[^"]*)".*context="(?<context>[^"]*)".*type="(?<type>[^"]*)".*thread="(?<thread>[^"]*)".*file="(?<file>[^"]*)"'
if ($line -match $regex) {
$user = $Matches.user
$date = $Matches.date
$time = $Matches.time
# ... now do stuff with your variables ...
}
You might want to build in some error checking etc. (e.g. when no line is found or does not match etc.)
Also you could greatly simplify the regex if you only need those 3 values. I designed it so that all fields from the line are included.
Also, you could convert the values into more appropriate types, which (depending on what you want to do with them afterwards) might make handling them easier:
$type = [int]$Matches.type
$credential = New-Object System.Net.NetworkCredential($Matches.user, $null, $Matches.domain)
$datetime = [DateTime]::ParseExact(($Matches.date + $Matches.time), "MM-dd-yyyyHH:mm:ss.fff+000", [CultureInfo]::InvariantCulture)

DNS name from IP address

I need to create a list of IP addresses and DNS names. I
am trying to get DNS names from IP addresses. I have tried two ways:
try/catch but it ends afterwards.
Without and it just outputs DNS names that I can't relate to the IP addresses.
Here's what I have so far:
#try {
Get-Content C:\Users\pintose\Documents\IP_Address.txt | ForEach-Object
{([system.net.dns]::GetHostByAddress($_)).hostname >> C:\Users\user\Documents\hostname.txt}
# }
# catch {
if ($_.Exception.Message -like "*The requested name is valid*") {
Write-Output "UNREACHABLE" | Out-File C:\Users\user\Documents\hostname.txt }
# }
Try this solution:
$outFile = "C:\Users\user\Documents\hostname.txt"
Get-Content C:\Users\pintose\Documents\IP_Address.txt | ForEach-Object {
$hash = #{ IPAddress = $_
hostname = "n/a"
}
$hash.hostname = ([system.net.dns]::GetHostByAddress($_)).hostname
$object = New-Object psobject -Property $hash
Export-CSV -InputObject $object -Path $outFile -Append -NoTypeInformation
}
We create a objects, that have the IPaddress in it and a hostname n/a if it cannot be resolved. Then, the object gets exported into the file. You'll get something like:
192.0.0.1; Server1
This uses a workflow so it can do parallel foreach
Workflow Get-DNSNames([string[]]$IPAddresses){
foreach -parallel ($IP in $IPAddresses){
try{
#{$IP = $(([system.net.dns]::GetHostByAddress($IP)).hostname)}
}catch{
#{$IP = "N/A"}
}
}
}
$List = Get-DNSNames -IPAddresses $(Get-Content "C:\IPAddresses.txt").Split("[\r\n]")
$List | Out-File "C:\IPAddresses_Complete.txt"
You might want to try the other solutions offered here, but here are some things you might want to think about.
First, I'd recommend not putting the try{}catch{} around the whole of the first command. If you are looping through data and just one of them causes an exception, you risk not completing the task. Put the try{}catch{} around just the "risky" line of code:
Get-Content C:\Users\pintose\Documents\IP_Address.txt | Foreach-Object {
try {
([system.net.dns]::GetHostByAddress($_)).hostname >> C:\Users\user\Documents\hostname.txt
}
catch {
if ($_.Exception.Message -like "*The requested name is valid*") {
Write-Output "UNREACHABLE" | Out-File C:\Users\user\Documents\hostname.txt
}
}
}
When you catch the exception, you only write to the text file in the case that "the requested name is valid" (do you mean invalid?). You never write anything to the file otherwise. Thus, going back to your original code:
IF there is an exception caused by ANY of the IP addresses
AND the exception is NOT "the requested name is valid" (which I think might be a typo?)
THEN no error gets written to the file and the script ends without necessarily completing all the IP addresses.
Other things:
You use two methods to write to the file: >> and Out-File. Probably better to use the PowerShell cmdlet but with the -Append switch to ensure you append to the end of the file:
([system.net.dns]::GetHostByAddress($_)).hostname | Out-File C:\Users\user\Documents\hostname.txt -Append
Write-Output "UNREACHABLE" | Out-File C:\Users\user\Documents\hostname.txt -Append
#restless1987 has suggested a way to ensure you write both the IP address and the hostname (if determined) to the output file. I'd have a look at that to work out what is going on.
My final tip would be to be wary of reading in from .txt files with Get-Content. They often have trailing (blank) lines and you might want to try to ignore such blanks. Probably not a big issue in this case as it will just mean a failed DNS attempt, but I have seen such things wreak havoc on every mailbox in a (very) large company when used with other commands.
Another way...
$ip_list = Get-Content ./IP_Address.txt
foreach ($ip in $ip_list) {
try {
([system.net.dns]::GetHostByAddress($ip)).hostname |
Out-File -FilePath ./hostname.txt -Append
}
catch {
if ($_.Exception.Message -like "*The requested name is valid*") {
Write-Output "UNREACHABLE" | Out-File -FilePath './hostname.txt' -Append }
}
}
There are many tools that can accomplish this, but if you need a quick and dirty solution that you can run just about anywhere this will get the job done.
Using the eternally useful ps tools such as psloggedon /accepteula \\computername or ip address you can get who is currently logged in to check if this is the correct machine. For example:
c:\pstools>psloggedon /accepteula \\10.0.0.10
loggedon v1.33 - See who's logged on
Copyright ⌐ 2000-2006 Mark Russinovich
Sysinternals - www.sysinternals.com
Users logged on locally:
Error: could not retrieve logon time
NT AUTHORITY\LOCAL SERVICE
Error: could not retrieve logon time
NT AUTHORITY\NETWORK SERVICE
1/12/2015 8:06:51 AM DOMAIN\user
Error: could not retrieve logon time
NT AUTHORITY\SYSTEM
Users logged on via resource shares:
1/17/2015 2:26:43 PM DOMAIN\user
Now that you have confirmed that this IP address is the correct one for this user. We just need to lookup the IP address using nslookup.
C:\>nslookup 10.0.0.10
Server: server.domain.com
Address: 10.10.0.1
Name: Workstation07.server.domain.com
Address: 10.0.0.10
Now we know that the computer name for that computer is Workstation07.

How to register the result of a PowerShell expression in a variable for a DSC?

I am trying to configure an Azure VM with Azure Automation DSC. One of the resources I want to set is DNS on the client workstation with xDnsServerAddress from xNetworking module.
The problem is that it requires an interface alias and interface aliases change on Azure VMs vary depending on deployment (mainly VMs seem to get either Ethernet or Ethernet 2).
I can query the interface name locally using the following cmdlet expression:
$Interface=Get-NetAdapter|Where Name -Like "Ethernet*"|Select-Object -First 1
$InterfaceAlias=$($Interface.Name)
I don't know, however, how to use it inside the DSC.
My DSC configuration is as below (only the relevant part):
Configuration MyDscConfig
{
Import-DscResource -ModuleName xNetworking
# place-1
Node $AllNodes.where{$_.Role -eq "Workstation"}.NodeName
{
# place-2
xDnsServerAddress DnsServerAddressSetToDc1
{
Address = '10.0.0.4'
InterfaceAlias = $InterfaceAlias
AddressFamily = 'IPv4'
Validate = $true
}
}
}
The problem is that if I place the cmdlet expression either in place-1 or place-2 the compilation job fails with:
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: The term 'Get-NetAdapter' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
I assume it tries to execute Get-NetAdapter on the pull server, but I may be misinterpreting the error message.
How can I register the result of the cmdlet expression on a destination machine and register it in $InterfaceAlias variable for the xDnsServerAddress resource?
You currently cannot perform a query keep the results of the operation and use it to declare the next state (See notes at the end of the answer.)
You can work around this limitation using the documented workaround/solution from xNetworking, which will find an active ethernet adapter named Ethernet1 if it does not, it will find the first active ethernet adapter and make sure it is named Ethernet1. Then, use a resource to set the DSC server address on Ethernet1.
This is investigational, names and parameters are subject to change. The DSC team is investigating a better way to do this.
Configuration SetDns
{
param
(
[string[]]$NodeName = 'localhost'
)
Import-DSCResource -ModuleName xNetworking
Node $NodeName
{
script NetAdapterName
{
GetScript = {
Import-module xNetworking
$getResult = Get-xNetworkAdapterName -Name 'Ethernet1'
return #{
result = $getResult
}
}
TestScript = {
Import-module xNetworking
Test-xNetworkAdapterName -Name 'Ethernet1'
}
SetScript = {
Import-module xNetworking
Set-xNetworkAdapterName -Name 'Ethernet1' -IgnoreMultipleMatchingAdapters
}
}
xDnsServerAddress DnsServerAddress
{
Address = '10.0.0.4'
InterfaceAlias = 'Ethernet1'
AddressFamily = 'IPv4'
DependsOn = #('[Script]NetAdapterName')
}
}
}
Notes:
There is a question in the comments. The summary of the question is if querying turns the declarative paradigm into an imperative paradigm.
Answer:
I don't believe querying turns it into an imperative paradigm, but you
currently cannot perform a query keep the results of the operation and
use it to declare the next state.
This currently forces us into something a little further away from
declarative for the problem that I would like. My personal opinion is that we
should work with what we have and write resources that query and set a
known state. Then, use the known state through the rest of the
configuration (a form a declarative-relative, per your terminology).
The DSC team has this similar UserVoice
suggestion
we are using to track this request. Please upvote it if you think this
is a useful feature.
It seems the DSC Node Configuration (which is a MOF file) must have all the values set at the time of compilation.
As a workaround I decided to use a PowerShell script resource instead of xDnsServerAddress (some values below are hardcoded to match the example in the question):
Script DnsServerAddressSetToDc1
{
GetScript = {
Return #{
Result = [string](get-DnsClientServerAddress -InterfaceAlias (Get-NetAdapter|Where Name -Like "Ethernet*"|Select-Object -First 1).Name -AddressFamily IPv4).ServerAddresses
}
}
TestScript = {
if (([string](get-DnsClientServerAddress -InterfaceAlias (Get-NetAdapter|Where Name -Like "Ethernet*"|Select-Object -First 1).Name -AddressFamily IPv4).ServerAddresses) -eq '10.0.0.4') {
Write-Verbose "DNS server set"
Return $true
} Else {
Write-Verbose "DNS Server not set"
Return $false
}
}
SetScript = {
Set-DnsClientServerAddress `
-InterfaceAlias (Get-NetAdapter|Where Name -Like "Ethernet*"|Select-Object -First 1).Name `
-ServerAddresses 10.0.0.4 `
-Validate `
-ErrorAction Stop
}
}

perform nslookup from PowerShell

I'm writing a powershell to exact the ip from a server name, which need me to embed the nslookup code into my powershell
how can I do the intergrating work?
Can any body help me?
Add-PSSnapin Microsoft.SharePoint.PowerShell
$web = Get-SPWeb -Identity “http://nycs00058260/sites/usitp“
$server_status = "PROD"
$list=$web.Lists[”DNS_Status”]
$items = $list.items
Foreach($item in $items){
$item_name = $item["Server_name"] #need to get the ip by this name
/*nslook up*/
$item_name.update()
}
If you install the PSCX module, it comes with a cmdlet Resolve-Host which handles name lookups.
Absent that, this one-liner will do the job
[System.Net.Dns]::GetHostAddresses("www.msn.com")
You can also pass in an IP address - but the results will be different.
See also http://blogs.msdn.com/b/powershell/archive/2006/06/26/647318.aspx & http://powershell.com/cs/media/p/210.aspx
PowerShell 3.0 on Windows 8 and higher comes with a Resolve-DnsName cmdlet that will get this information:
(Resolve-DnsName $server_name)[0].IpAddress
Simply use :
Resolve-DnsName monServer | ? { # make selection here } | % { $_.IPAdress } | select-object -first 1
#Here is a far better method for nslookup
# HOWTO ensure an nslookup results no errors but still gives the original names and column separations
$day = Get-Date -Format yyyyMMdd #<-- HOWTO set the day variable for today
$ErrorActionPreference = ‘SilentlyContinue’ #<-- HOWTO turn off error messages
$WarningActionPreference = 'SilentlyContinue' #<-- HOWTO turn off warning messages
$servers = Get-Content .\input\servers.txt
Foreach ($server in $servers){
$result = Resolve-DnsName $server -Server $env:LOGONSERVER.Remove(0,2) -Type ALL #<-- NSLOOKUP using your logon server
write-host ($server+","+$result.Name+","+$result.IPAddress) #<-- HOWTO Write two variables separated by a comma
}
$ErrorActionPreference = ‘SilentlyContinue’ #HOWTO turn on error messages
$WarningActionPreference = 'SilentlyContinue' #HOWTO turn on warning messages

Display current time with time zone in PowerShell

I'm trying to display the local time on my system with the TimeZone. How can I display time in this format the simplest way possible on any system?:
Time: 8:00:34 AM EST
I'm currently using the following script:
$localtz = [System.TimeZoneInfo]::Local | Select-Object -expandproperty Id
if ($localtz -match "Eastern") {$x = " EST"}
if ($localtz -match "Pacific") {$x = " PST"}
if ($localtz -match "Central") {$x = " CST"}
"Time: " + (Get-Date).Hour + ":" + (Get-Date).Minute + ":" + (Get-Date).Second + $x
I'd like to be able to display the time without relying on simple logic, but be able to give the local timezone on any system.
While this is a bit ... naive perhaps, it's one way to get an abbreviation without a switch statement:
[Regex]::Replace([System.TimeZoneInfo]::Local.StandardName, '([A-Z])\w+\s*', '$1')
My regular expression probably leaves something to be desired.
The output of the above for my time zone is EST. I did some looking as I wanted to see what the value would be for other GMT offset settings, but .NET doesn't seem to have very good links between DateTime and TimeZoneInfo, so I couldn't just programmatically run through them all to check. This might not work properly for some of the strings that come back for StandardName.
EDIT: I did some more investigation changing the time zone on my computer manually to check this and a TimeZoneInfo for GMT+12 looks like this:
PS> [TimeZoneInfo]::Local
Id : UTC+12
DisplayName : (GMT+12:00) Coordinated Universal Time+12
StandardName : UTC+12
DaylightName : UTC+12
BaseUtcOffset : 12:00:00
SupportsDaylightSavingTime : False
Which produces this result for my code:
PS> [Regex]::Replace([System.TimeZoneInfo]::Local.StandardName, '([A-Z])\w+\s*', '$1')
U+12
So, I guess you'd have to detect whether the StandardName appears to be a set of words or just offset designation because there's no standard name for it.
The less problematic ones outside the US appear to follow the three-word format:
PS> [TimeZoneInfo]::Local
Id : Tokyo Standard Time
DisplayName : (GMT+09:00) Osaka, Sapporo, Tokyo
StandardName : Tokyo Standard Time
DaylightName : Tokyo Daylight Time
BaseUtcOffset : 09:00:00
SupportsDaylightSavingTime : False
PS> [Regex]::Replace([System.TimeZoneInfo]::Local.StandardName, '([A-Z])\w+\s*', '$1')
TST
You should look into DateTime format strings. Although I'm not sure they can return a time zone short name, you can easily get an offset from UTC.
$formatteddate = "{0:h:mm:ss tt zzz}" -f (get-date)
This returns:
8:00:34 AM -04:00
Be loath to define another datetime format! Use an existing one, such as RFC 1123. There's even a PowerShell shortcut!
Get-Date -format r
Thu, 14 Jun 2012 16:44:18 GMT
Ref.: Get-Date
This is a better answer:
$A = Get-Date #Returns local date/time
$B = $A.ToUniversalTime() #Convert it to UTC
# Figure out your current offset from UTC
$Offset = [TimeZoneInfo]::Local | Select BaseUtcOffset
#Add the Offset
$C = $B + $Offset.BaseUtcOffset
$C.ToString()
Output:
3/20/2017 11:55:55 PM
I'm not aware of any object that can do the work for you. You could wrap the logic in a function:
function Get-MyDate{
$tz = switch -regex ([System.TimeZoneInfo]::Local.Id){
Eastern {'EST'; break}
Pacific {'PST'; break}
Central {'CST'; break}
}
"Time: {0:T} $tz" -f (Get-Date)
}
Get-MyDate
Or even take the initials of the time zone id:
$tz = -join ([System.TimeZoneInfo]::Local.Id.Split() | Foreach-Object {$_[0]})
"Time: {0:T} $tz" -f (Get-Date)
I just combined several scripts and finally was able to run the script in my domain controller.
The script provides the output of time and timezone for all the machines connected under the domain.
We had a major issue with our application servers and used this script to cross check the time and timezone.
# The below scripts provides the time and time zone for the connected machines in a domain
# Appends the output to a text file with the time stamp
# Checks if the host is reachable or not via a ping command
Start-Transcript -path C:\output.txt -append
$ldapSearcher = New-Object directoryservices.directorysearcher;
$ldapSearcher.filter = "(objectclass=computer)";
$computers = $ldapSearcher.findall();
foreach ($computer in $computers)
{
$compname = $computer.properties["name"]
$ping = gwmi win32_pingstatus -f "Address = '$compname'"
$compname
if ($ping.statuscode -eq 0)
{
try
{
$ErrorActionPreference = "Stop"
Write-Host “Attempting to determine timezone information for $compname…”
$Timezone = Get-WMIObject -class Win32_TimeZone -ComputerName $compname
$remoteOSInfo = gwmi win32_OperatingSystem -computername $compname
[datetime]$remoteDateTime = $remoteOSInfo.convertToDatetime($remoteOSInfo.LocalDateTime)
if ($Timezone)
{
foreach ($item in $Timezone)
{
$TZDescription = $Timezone.Description
$TZDaylightTime = $Timezone.DaylightName
$TZStandardTime = $Timezone.StandardName
$TZStandardTime = $Timezone.StandardTime
}
Write-Host "Timezone is set to $TZDescription`nTime and Date is $remoteDateTime`n**********************`n"
}
else
{
Write-Host ("Something went wrong")
}
}
catch
{
Write-Host ("You have insufficient rights to query the computer or the RPC server is not available.")
}
finally
{
$ErrorActionPreference = "Continue"
}
}
else
{
Write-Host ("Host $compname is not reachable from ping `n")
}
}
Stop-Transcript
Russia, France, Norway, Germany:
get-date -format "HH:mm:ss ddd dd'.'MM'.'yy' г.' zzz"
Output for Russian time zone: 22:47:27 Чт 21.11.19 г. +03:00
Others - just change the code.
If you have a internet connection... API
Function tzAbbreviation {
Try {
$webData = Invoke-WebRequest -Uri "https://worldtimeapi.org/api/ip" -UseBasicParsing -TimeoutSec 3 -ErrorAction Stop
$Content = ConvertFrom-Json $webData.Content
Return $($Content.Abbreviation)
}
Catch {}
}
$tzAbbreviation = tzAbbreviation
In the Netherlands...
Output: CET