I am trying to automate the process of importing GNAF (location dataset for Australia) into a new database.
The problem I am having is that when I try to the database and recreate it I get an error saying the database is in use:
Invoke-Sqlcmd : Cannot drop database "GNAF" because it is currently in use. At line:11 char:5
+ Invoke-Sqlcmd -Query "IF EXISTS (SELECT name FROM master.dbo.sysd ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionException
+ FullyQualifiedErrorId : SqlError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand Invoke-Sqlcmd : Database 'GNAF' already exists. Choose a different database name. At line:15 char:5
+ Invoke-Sqlcmd -Query "CREATE DATABASE GNAF" -serverinstance $Serv ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionException
+ FullyQualifiedErrorId : SqlError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
In the activity monitor for SSMS it shows:
This is the function that is being called:
function importQNAF {
$ScriptPath = "C:\Users\owain.esau\Scripts\PowerShell\SQL-Server\TRIS Database Anon\" # change to pwd for live
$QNAFpath1 = ( $ScriptPath + "GNAF\G-NAF\G-NAF AUGUST 2017\Standard\" )
$QNAFpath2 = ( $ScriptPath + "GNAF\G-NAF\G-NAF AUGUST 2017\Authority Code\" )
$GNAF_ImportData_Path = ( $ScriptPath + "\SQLScripts\GNAF_ImportData.sql" )
(Get-Content $GNAF_ImportData_Path).replace("Powershell To Change#1", $QNAFpath1) | Set-Content $GNAF_ImportData_Path
(Get-Content $GNAF_ImportData_Path).replace("Powershell To Change#2", $QNAFpath2) | Set-Content $GNAF_ImportData_Path
## Drop and Create Table
Invoke-Sqlcmd -Query "IF EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name ='GNAF')
BEGIN
ALTER DATABASE GNAF SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE GNAF;
END
;" -serverinstance $ServerAddress;
Start-Sleep -s 5;
Invoke-Sqlcmd -Query "CREATE DATABASE GNAF" -serverinstance $ServerAddress;
Start-Sleep -s 5;
Invoke-Sqlcmd -InputFile ( $ScriptPath + "SQLScripts\GNAF_CreateTables.sql" ) -serverinstance $ServerAddress;
try {
Invoke-Sqlcmd -InputFile ( $ScriptPath + "SQLScripts\GNAF_ImportData.sql" ) -serverinstance $ServerAddress -querytimeout 0;
} catch {
if ($error -match "Access is denied.") {
Write-Host "[!] ERROR: Access denied to GNAF folder."
Write-Host "----------------------------------------"
Write-Host "You have two options: "
Write-Host ""
Write-Host " 1) Give MSSQLSERVER full access to" $ScriptPath
Write-Host " 2) Copy the1 folder: " $ScriptPath"GNAF to the MSSQL Source folder (C:\Program Files\SQL Server)"
Do {
switch(Read-Host "Please choose an action (1 or 2)") {
1 {
$Acl = (Get-Item $ScriptPath).GetAccessControl('Access');
$Ar = New-Object System.Security.AccessControl.FileSystemAccessRule("NT SERVICE\MSSQLSERVER", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow");
$Acl.SetAccessRule($Ar);
Set-Acl $ScriptPath $Acl;
importQNAF;
}
2 { Write-Host "Not coded yet"; }
default { "Invalid Entry, must be 1 or 2"; }
}
} Until ($choice -eq 1 -or $choice -eq 2)
}
}
(Get-Content $GNAF_ImportData_Path).replace($QNAFpath1, "Powershell To Change#1") | Set-Content $GNAF_ImportData_Path
(Get-Content $GNAF_ImportData_Path).replace($QNAFpath2, "Powershell To Change#2") | Set-Content $GNAF_ImportData_Path
}
The script I am using to actually import the data into SQL server is almost identical to the one found on this page:
https://www.rednotebluenote.com/2016/04/importing-psma-geocoded-national-address-file-g-naf-to-sql-server/
It works fine directly in SSMS. Also, I don't think my error match is picking up the correct error, instead it is matching to all errors.
--- Update ----------------------------------------------
Managed to get it to run when i restart the MSSQLSERVER service at the start of the function.
The problem now is that it times out:
Invoke-Sqlcmd : Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
BULK INSERT [MB_2011] FROM 'C:\Users\owain.esau\OneDrive\Scripts\PowerShell\SQL-Server\TRIS Database Anon\GNAF\G-NAF\G-NAF AUGUST 2017\Standard\ACT_MB_2011_psv.psv' WITH ( FIELDTERMINATOR = '|', ROWTERMINATOR =
'\n', FIRSTROW = 2, ROWS_PER_BATCH=100000, TABLOCK)
At line:30 char:9
+ Invoke-Sqlcmd -InputFile ( $ScriptPath + "SQLScripts\GNAF_Imp ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionException
+ FullyQualifiedErrorId : SqlError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
It only runs for around 45 seconds before this error pops up, the actual script being run takes around 3 - 5 minutes run in SSMA.
(The function has been updated a bit)
You need to terminate any existing, open connections to the database.
Here's the easiest method (it basically forces all other connections to be killed)
IF EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name ='GNAF')
BEGIN
ALTER DATABASE GNAF SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE GNAF;
END
;
Related
I am running into an issue with doing a Powershell SQL restore. I have this code
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoEnum") | Out-Null
$backupFile = 'C:\Temp\User_20191029152532.bak'
$server = New-Object ("Microsoft.SqlServer.Management.Smo.Server") "(local)"
$backupDevice = New-Object("Microsoft.SqlServer.Management.Smo.BackupDeviceItem") ($backupFile, "File")
$smoRestore = new-object("Microsoft.SqlServer.Management.Smo.Restore")
$smoRestore.NoRecovery = $false;
$smoRestore.ReplaceDatabase = $true;
$smoRestore.Action = "Database"
$smoRestorePercentCompleteNotification = 10;
$smoRestore.Devices.Add($backupDevice)
$smoRestoreDetails = $smoRestore.ReadBackupHeader($server)
"Database Name from Backup Header : " +$smoRestoreDetails.Rows[0]["DatabaseName"]
$smoRestore.Database =$smoRestoreDetails.Rows[0]["DatabaseName"]
$smoRestoreFile = New-Object("Microsoft.SqlServer.Management.Smo.RelocateFile")
$smoRestoreLog = New-Object("Microsoft.SqlServer.Management.Smo.RelocateFile")
$smoRestoreFile.LogicalFileName = $smoRestoreDetails.Rows[0]["DatabaseName"]
$smoRestoreFile.PhysicalFileName = $server.Information.MasterDBPath + "\" + $smoRestore.Database + "_Data.mdf"
$smoRestoreLog.LogicalFileName = $smoRestoreDetails.Rows[0]["DatabaseName"] + "_Log"
$smoRestoreLog.PhysicalFileName = $server.Information.MasterDBLogPath + "\" + $smoRestore.Database + "_Log.ldf"
$smoRestore.RelocateFiles.Add($smoRestoreFile)
$smoRestore.RelocateFiles.Add($smoRestoreLog)
$smoRestore.SqlRestore($server)
Everything works fine if I run this. However, I would like to provide a variable for which backup to restore. So I changed the top part of the code to this:
$path = 'C:\Temp'
$db_name = 'User'
$file = Get-ChildItem $path '*.bak' | Select-Object basename | Where-Object {$_.basename -like $db_name + '*' }
$backupFile = $path + '\' + $file
When I make this change, I start getting the below error.
Exception calling "ReadBackupHeader" with "1" argument(s): "An exception occurred while executing a Transact-SQL statement or batch."
At line:23 char:1
+ $smoRestoreDetails = $smoRestore.ReadBackupHeader($server)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ExecutionFailureException
Not really sure why it will not accept the $file variable that I am passing. If I look at the variable, it is returning the right value.
Stupid issue on my part. $file did not include the extension. Adding the .bak to $file fixed the issue.
I have a simple SQL file which creates a new database based on the 3 input parameters sent: dbName, datafileName, and logfileName
But, when I invoke this script via powershell by calling the Invoke-Sqlcmd cmdlet, I get the following error:
Incorrect syntax near 'CommonDB'.
Incorrect syntax near '\'.
Incorrect syntax near 'CommonDB'.
Incorrect syntax near 'E:'.
The label 'E' has already been declared. Label names must be unique within a query batch or stored procedure.
Incorrect syntax near 'E:'.
The label 'E' has already been declared. Label names must be unique within a query batch or stored procedure.
Incorrect syntax near 'CommonDB'.
The label 'E' has already been declared. Label names must be unique within a query batch or stored procedure.
The label 'E' has already been declared. Label names must be unique within a query batch or stored procedure.
At line:1 char:1
+ Invoke-Sqlcmd -ServerInstance localhost\sql2012 -Database master -Use ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionException
+ FullyQualifiedErrorId : SqlError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
My Powershell version:
Major Minor Build Revision
----- ----- ----- --------
5 1 16299 1004
This is my CreateDatabase.sql file:
USE MASTER
GO
SET QUOTED_IDENTIFIER ON;
GO
DECLARE
#DbName nvarchar(50),
#sql nvarchar(max),
#datafileName nvarchar(500),
#logfileName nvarchar(500)
SET #sql = N''
SELECT #DbName = N'$(dbName)'
SELECT #datafileName = N'$(datafileName)'
SELECT #logfileName = N'$(logfileName)'
IF NOT EXISTS (SELECT * FROM master.sys.databases WHERE name = #DbName)
BEGIN
SELECT #sql =
N'CREATE DATABASE "' + #DbName + N'"'
+ N' ON PRIMARY ( NAME = [' + #DbName + '] ,'
+ N' FILENAME = [' + #datafileName + '],'
+ N' SIZE = 1GB , MAXSIZE = UNLIMITED, FILEGROWTH = 512MB )'
+ N' LOG ON ( NAME = [' + #DbName + '_log] ,'
+ N' FILENAME = [' + #logfileName + '], SIZE = 100MB , FILEGROWTH = 10% )'
END
IF (#sql <> N'')
EXEC (#sql)
GO
SET QUOTED_IDENTIFIER OFF;
GO
This is my PS1 file Contents:
$params=#("dbName='CommonDB'", "datafileName='E:\my files\sql.2012\data\CommonDb.mdf'", "logfileName='E:\my files\sql.2012\data\CommonDb_log.ldf'")
Invoke-Sqlcmd -ServerInstance localhost\sql2012 -Database master -Username sa -Password xxxx -ErrorAction Stop -InputFile .\CreateDatabase.sql -Variable $params
But, if I run this from the PS prompt, it runs just fine:
PS E:\My Scripts> Invoke-Sqlcmd -ServerInstance localhost\sql2012 -Database master -Username sa -Password xxxx -ErrorAction Stop -InputFile .\CreateDatabase.sql -Variable dbName='CommonDB', datafileName='E:\my files\sql.2012\data\CommonDb.mdf', logfileName='E:\my files\sql.2012\data\CommonDb_log.ldf'
Try :
$params=#{"dbName"="CommonDB"; "datafileName"="E:\my files\sql.2012\data\CommonDb.mdf"; "logfileName"="E:\my files\sql.2012\data\CommonDb_log.ldf"}
OR
$dbName = "CommonDB"
$datafileName = "E:\my files\sql.2012\data\CommonDb.mdf"
$logfileName = "E:\my files\sql.2012\data\CommonDb_log.ldf"
Invoke-Sqlcmd -ServerInstance localhost\sql2012 -Database master -Username sa -Password xxxx -ErrorAction Stop -InputFile .\CreateDatabase.sql -Variable dbName=$dbName, datafileName=$datafileName, logfileName=$logfileName
Ok, I got this thing to work in a generic way by generating the script needed to invoke-sqlcmd. Wrote a function which takes in the script file and its parameters and generates a ScriptBlock from the same:
<#
.SYNOPSIS
#
.DESCRIPTION
This function invokes sql in a .sql file and passes the parameters to it as needed.
.PARAMETER script_file
$script_file - The relative or absolute path to the sql file that needs to be executed
$parameter_names - a list of string containing the names of the parameters which need to be passed to the sql file
.EXAMPLE
For example if there is a SQL file which takes 3 parameters: dbName, datafileName, and logfileName, whose values are:
"CommonDB", "E:\my files\sql.2012\data\CommonDb.mdf", and "E:\my files\sql.2012\data\CommonDb_log.ldf" repectively,
this function first generates the following ScriptBlock for the same and then executes it by calling the Invoke-Command cmdlet:
$dbName = "CommonDB"
$datafileName = "E:\my files\sql.2012\data\CommonDb.mdf"
$logfileName = "E:\my files\sql.2012\data\CommonDb_log.ldf"
Invoke-Sqlcmd -ServerInstance localhost\sql2012 -Database master -Username sa -Password xxxx -ErrorAction Stop -InputFile .\CreateDatabase.sql -Variable dbName=$dbName, datafileName=$datafileName, logfileName=$logfileName
#>
Function Invoke-SQLFile( [string] $script_file, [System.Collections.Generic.List[string]]$parameter_names) {
#Assuming that $serverInstance, $db, $username, and $password are global variables
$invokeSql_Script = "Invoke-Sqlcmd -ServerInstance `"$serverInstance`" -Database `"$db`" -Username `"$username`" -Password `"$password`" -ErrorAction Stop -InputFile `"$script_file`" "
$scriptStr = ""
#The script file may or may not accept parameters
if(($null -ne $parameter_names) -and ($parameter_names.Count -gt 0)){
$invokeSqlVariableStr = ""
foreach($param in $parameter_names){
#There would be a global variable with the same name as the parameter_name
# e.g. If the param name is "databaseName", there should be a global variable called $databaseName
$paramVal = Get-Variable -Name $param | Select-Object -Property Value
$scriptStr = "$scriptStr `$$param = `"$($paramVal.Value)`";`r`n"
$invokeSqlVariableStr = "$invokeSqlVariableStr $param=`"`$$param`", "
}
#remove the trailing ".," from the string
$invokeSqlVariableStr = $invokeSqlVariableStr -replace ".{2}$"
$scriptStr = "$scriptStr $invokeSql_Script -Variable $invokeSqlVariableStr"
}
else {
#if no parameters need to be passed, then the $invokeSql_Script is good enough
$scriptStr = $invokeSql_Script
}
$scriptBlock = [System.Management.Automation.ScriptBlock]::Create($scriptStr)
Invoke-Command -ScriptBlock $scriptBlock
}
Thanks to #rAJ for this idea.
However, I still don't know why my initial script was failing. And pointers there is appreciated.
I've got next error when I'm trying to create database script:
Exception calling "EnumScriptTransfer" with "0" argument(s): "Script
transfer failed. " At C:\temp\Test.ps1:126 char:5
+ $transfer.EnumScriptTransfer();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : FailedOperationException
Same script is working properly on the other computer. So I assume that the reason is in SQL server version, or rights.
Any ideas?
Code:
echo "Scripting database $dbname .."
set-psdebug -strict
$ErrorActionPreference = "stop" # you can opt to stagger on, bleeding, if an error occurs
# Load SMO assembly, and if we're running SQL 2008 DLLs load the SMOExtended and SQLWMIManagement libraries
$ms='Microsoft.SqlServer'
$v = [System.Reflection.Assembly]::LoadWithPartialName( "$ms.SMO")
$ver = (($v.FullName.Split(','))[1].Split('='))[1];
$vtmp=$ver.Split('.')[0];
echo " SMO version is $ver"
if ($vtmp -ge 9) {
echo " Running SQL 2008, load the SMOExtended .."
[System.Reflection.Assembly]::LoadWithPartialName("$ms.SMOExtended") | out-null
}
$My="$ms.Management.Smo" #
$s = new-object ("$My.Server") $DataSource
if ($s.Version -eq $null ){Throw "Can't find the instance $Datasource"}
$ver = $s.Version
echo " SQL Server version is $ver"
$db= $s.Databases[$Database]
if ($db.name -ne $Database){Throw "Can't find the database '$Database' in $Datasource"};
$transfer = new-object ("$My.Transfer") $db
if ($transfer -eq $null ){Throw "Can't create transfer object"}
$transfer.Options.ScriptBatchTerminator = $true # this only goes to the file
$transfer.Options.ToFileOnly = $true # this only goes to the file
$transfer.Options.ScriptData = $true;
$transfer.Options.FileName = $BackupFile;
$tmp = $transfer.Options
echo " Transfer options are: $tmp"
echo " $transfer"
$transfer.EnumScriptTransfer();
Output:
Scripting database MyDbName ..
SMO version is 14.0.0.0
SQL Server version is 14.0.1000
Transfer options are: ScriptingOptions: ScriptBatchTerminator, ToFileOnly, SchemaQualify, WithDependencies, AllowSystem
Objects, AgentJobId, NoVardecimal, ScriptSchema, ScriptData, ScriptDataCompression, PrimaryObject
Microsoft.SqlServer.Management.Smo.Transfer
Exception calling "EnumScriptTransfer" with "0" argument(s): "Script transfer failed. "
At C:\temp\Test.ps1:126 char:5
+ $transfer.EnumScriptTransfer();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : FailedOperationException
Tried to add try-catch block, but this does not work. No idea why:
Try
{
$transfer.EnumScriptTransfer();
}
Catch
{
$msg = $_.Exception.Message;
$innerMsg = $_.Exception.InnerException.Message;
echo $msg
echo $innerMsg
throw;
}
I'm trying to build function that extracts information about SQL Server Instance and updates a table with the information:
Import-Module "sqlps" -DisableNameChecking
. "D:\V1\CMS\Get-SQLInstance.ps1"
$serverName = 'VIRTSQL120S'
$tableName = 'SQL_SERVER_PRD'
function Update-Table ($srv, $tableName) {
$get_sql_info = Get-SQLInstance -Computername $serverName
foreach ($serverinstance in $get_sql_info) {
$connectionstring=$serverinstance.FullName
$instance=$serverinstance.SQLInstance
$version=$serverinstance.Version
$edition=$serverinstance.edition
$caption=$serverinstance.Caption
$port=$serverinstance.Instance_Port
Invoke-Sqlcmd -ServerInstance 'VIRTSQL152S' -Database 'SQL_info' -Query "
UPDATE $tableName SET instance = '$instance',
connection_name = '$connectionstring',
version = '$version',
edition = '$edition',
caption = '$caption',
port = '$port'
WHERE servername = '$serverName';"
}
}
Update-Table $serverName, $tableName`
The error that I'm getting is:
Invoke-Sqlcmd : Incorrect syntax near the keyword 'SET'.
At line:19 char:13
+ Invoke-Sqlcmd -ServerInstance 'VIRTSQL152S' -Database >'SQL_info' -Qu ...
+ >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], >SqlPowerShellSqlExecutionException
+ FullyQualifiedErrorId : >SqlError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
If I execute this outside the function it works with no issues. Any help?
Thanks.
I've tried with a query variable and the error is the same:
$query = "UPDATE $tableName SET instance = '$instance',`
connection_name = '$connectionstring',`
version = '$version',`
edition = '$edition',`
caption = '$caption',`
port = '$port'`
WHERE servername = '$serverName';"
I have a PowerShell script which works well when I run it on server with SQL Server default instance (MSSQLSERVER) but the same script fails on a server with a named instance (MSSQL$instance)
For the default instance (MSSQLSERVER)
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$service = Get-service -name 'MSSQLSERVER'
$status = $service.status
$CreateDB = "db-Test"
if ( $status -eq "Running" )
{
'Success' | Out-File -FilePath c:\sqltest.log -Encoding ASCII
$srv = new-Object Microsoft.SqlServer.Management.Smo.Server("(local)")
$db = New-Object Microsoft.SqlServer.Management.Smo.Database($srv, "$CreateDB")
$db.Create()
$db.CreateDate
}
else
{
'Failed' | Out-File -FilePath c:\sqltest.log -Encoding ASCII
}
Above script works very well. But below script throws error :
For named instance :
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$service = Get-service -name 'MSSQL$instancename'
$status = $service.status
$CreateDB = "db-Test"
if ( $status -eq "Running" )
{
'Success' | Out-File -FilePath c:\sqltest.log -Encoding ASCII
$srv = new-Object Microsoft.SqlServer.Management.Smo.Server("(local)")
$db = New-Object Microsoft.SqlServer.Management.Smo.Database($srv, "$CreateDB")
$db.Create()
$db.CreateDate
}
else
{
'Failed' | Out-File -FilePath c:\sqltest.log -Encoding ASCII
}
Above script for named SQL instance throws below error :
New-Object : Exception calling ".ctor" with "2" argument(s): "SetParent failed for Database 'Netmagic-Test'. "
At C:\mssql-test.ps1:11 char:7
+ $db = New-Object Microsoft.SqlServer.Management.Smo.Database($srv, "$CreateDB")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
You cannot call a method on a null-valued expression.
At C:\mssql-test.ps1:12 char:1
+ $db.Create()
+ ~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Error Screen
Thanks,
Viral